Merge branch 'master' into feature-activitypub

This commit is contained in:
6543 2022-05-09 19:47:40 +02:00
commit 07150b33ba
No known key found for this signature in database
GPG key ID: C99B82E40B027BAE
1441 changed files with 45132 additions and 28797 deletions

View file

@ -5,66 +5,10 @@
package analyze
import (
"regexp"
"sort"
"strings"
"github.com/go-enry/go-enry/v2/data"
"github.com/go-enry/go-enry/v2"
)
var isVendorRegExp *regexp.Regexp
func init() {
matchers := data.VendorMatchers
caretStrings := make([]string, 0, 10)
caretShareStrings := make([]string, 0, 10)
matcherStrings := make([]string, 0, len(matchers))
for _, matcher := range matchers {
str := matcher.String()
if str[0] == '^' {
caretStrings = append(caretStrings, str[1:])
} else if str[0:5] == "(^|/)" {
caretShareStrings = append(caretShareStrings, str[5:])
} else {
matcherStrings = append(matcherStrings, str)
}
}
sort.Strings(caretShareStrings)
sort.Strings(caretStrings)
sort.Strings(matcherStrings)
sb := &strings.Builder{}
sb.WriteString("(?:^(?:")
sb.WriteString(caretStrings[0])
for _, matcher := range caretStrings[1:] {
sb.WriteString(")|(?:")
sb.WriteString(matcher)
}
sb.WriteString("))")
sb.WriteString("|")
sb.WriteString("(?:(?:^|/)(?:")
sb.WriteString(caretShareStrings[0])
for _, matcher := range caretShareStrings[1:] {
sb.WriteString(")|(?:")
sb.WriteString(matcher)
}
sb.WriteString("))")
sb.WriteString("|")
sb.WriteString("(?:")
sb.WriteString(matcherStrings[0])
for _, matcher := range matcherStrings[1:] {
sb.WriteString(")|(?:")
sb.WriteString(matcher)
}
sb.WriteString(")")
combined := sb.String()
isVendorRegExp = regexp.MustCompile(combined)
}
// IsVendor returns whether or not path is a vendor path.
func IsVendor(path string) bool {
return isVendorRegExp.MatchString(path)
return enry.IsVendor(path)
}

View file

@ -14,7 +14,10 @@ import (
)
func TestMain(m *testing.M) {
unittest.MainTest(m, filepath.Join("..", ".."), "")
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", ".."),
FixtureFiles: []string{""}, // load nothing
})
}
type testItem1 struct {

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build pam
// +build pam
package pam
@ -34,10 +33,10 @@ func Auth(serviceName, userName, passwd string) (string, error) {
if err = t.Authenticate(0); err != nil {
return "", err
}
if err = t.AcctMgmt(0); err != nil {
return "", err
}
return "", err
}
// PAM login names might suffer transformations in the PAM stack.
// We should take whatever the PAM stack returns for it.

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !pam
// +build !pam
package pam

View file

@ -1,5 +1,4 @@
//go:build pam
// +build pam
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build test_avatar_identicon
// +build test_avatar_identicon
package identicon

View file

@ -279,7 +279,7 @@ func EntryIcon(entry *git.TreeEntry) string {
}
return "file-symlink-file"
case entry.IsDir():
return "file-directory"
return "file-directory-fill"
case entry.IsSubModule():
return "file-submodule"
}

View file

@ -5,6 +5,7 @@
package cache
import (
"errors"
"fmt"
"strconv"
@ -34,25 +35,37 @@ func NewContext() error {
if conn, err = newCache(setting.CacheService.Cache); err != nil {
return err
}
const testKey = "__gitea_cache_test"
const testVal = "test-value"
if err = conn.Put(testKey, testVal, 10); err != nil {
if err = Ping(); err != nil {
return err
}
val := conn.Get(testKey)
if valStr, ok := val.(string); !ok || valStr != testVal {
// If the cache is full, the Get may not read the expected value stored by Put.
// Since we have checked that Put can success, so we just show a warning here, do not return an error to panic.
log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'",
setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn,
testVal, val,
)
}
}
return err
}
// Ping checks if the cache service works or not, it not, it returns an error
func Ping() error {
if conn == nil {
return errors.New("cache not available")
}
var err error
const testKey = "__gitea_cache_test"
const testVal = "test-value"
if err = conn.Put(testKey, testVal, 10); err != nil {
return err
}
val := conn.Get(testKey)
if valStr, ok := val.(string); !ok || valStr != testVal {
// If the cache is full, the Get may not read the expected value stored by Put.
// Since we have checked that Put can success, so we just show a warning here, do not return an error to panic.
log.Warn("cache (adapter:%s, config:%s) doesn't seem to work correctly, set test value '%v' but get '%v'",
setting.CacheService.Cache.Adapter, setting.CacheService.Cache.Conn,
testVal, val,
)
}
return nil
}
// GetCache returns the currently configured cache
func GetCache() mc.Cache {
return conn

View file

@ -6,6 +6,7 @@ package cache
import (
"fmt"
"strconv"
"time"
"code.gitea.io/gitea/modules/graceful"
@ -13,7 +14,6 @@ import (
"gitea.com/go-chi/cache"
"github.com/go-redis/redis/v8"
"github.com/unknwon/com"
)
// RedisCacher represents a redis cache adapter implementation.
@ -24,20 +24,37 @@ type RedisCacher struct {
occupyMode bool
}
// Put puts value into cache with key and expire time.
// toStr convert string/int/int64 interface to string. it's only used by the RedisCacher.Put internally
func toStr(v interface{}) string {
if v == nil {
return ""
}
switch v := v.(type) {
case string:
return v
case []byte:
return string(v)
case int:
return strconv.FormatInt(int64(v), 10)
case int64:
return strconv.FormatInt(v, 10)
default:
return fmt.Sprint(v) // as what the old com.ToStr does in most cases
}
}
// Put puts value (string type) into cache with key and expire time.
// If expired is 0, it lives forever.
func (c *RedisCacher) Put(key string, val interface{}, expire int64) error {
// this function is not well-designed, it only puts string values into cache
key = c.prefix + key
if expire == 0 {
if err := c.c.Set(graceful.GetManager().HammerContext(), key, com.ToStr(val), 0).Err(); err != nil {
if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), 0).Err(); err != nil {
return err
}
} else {
dur, err := time.ParseDuration(com.ToStr(expire) + "s")
if err != nil {
return err
}
if err = c.c.Set(graceful.GetManager().HammerContext(), key, com.ToStr(val), dur).Err(); err != nil {
dur := time.Duration(expire) * time.Second
if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), dur).Err(); err != nil {
return err
}
}

14
modules/container/map.go Normal file
View file

@ -0,0 +1,14 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package container
// KeysInt64 returns keys slice for a map with int64 key
func KeysInt64(m map[int64]struct{}) []int64 {
keys := make([]int64, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}

View file

@ -8,20 +8,18 @@ package context
import (
"context"
"fmt"
"html"
"net/http"
"net/url"
"strings"
"code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
auth_service "code.gitea.io/gitea/services/auth"
"gitea.com/go-chi/session"
)
// APIContext is a specific context for API service
@ -100,7 +98,7 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
if status == http.StatusInternalServerError {
log.ErrorWithSkip(1, "%s: %s", title, message)
if setting.IsProd && !(ctx.User != nil && ctx.User.IsAdmin) {
if setting.IsProd && !(ctx.Doer != nil && ctx.Doer.IsAdmin) {
message = ""
}
}
@ -117,7 +115,7 @@ func (ctx *APIContext) InternalServerError(err error) {
log.ErrorWithSkip(1, "InternalServerError: %v", err)
var message string
if !setting.IsProd || (ctx.User != nil && ctx.User.IsAdmin) {
if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) {
message = err.Error()
}
@ -191,33 +189,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
}
}
// SetTotalCountHeader set "X-Total-Count" header
func (ctx *APIContext) SetTotalCountHeader(total int64) {
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
}
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) {
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
if len(val) != 0 {
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
} else {
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
}
}
// RequireCSRF requires a validated a CSRF token
func (ctx *APIContext) RequireCSRF() {
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
if len(headerToken) > 0 || len(formValueToken) > 0 {
Validate(ctx.Context, ctx.csrf)
} else {
ctx.Context.Error(401, "Missing CSRF token.")
}
}
// CheckForOTP validates OTP
func (ctx *APIContext) CheckForOTP() {
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
@ -225,7 +196,7 @@ func (ctx *APIContext) CheckForOTP() {
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
twofa, err := auth.GetTwoFactorByUID(ctx.Context.User.ID)
twofa, err := auth.GetTwoFactorByUID(ctx.Context.Doer.ID)
if err != nil {
if auth.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
@ -239,7 +210,7 @@ func (ctx *APIContext) CheckForOTP() {
return
}
if !ok {
ctx.Context.Error(401)
ctx.Context.Error(http.StatusUnauthorized)
return
}
}
@ -248,18 +219,18 @@ func (ctx *APIContext) CheckForOTP() {
func APIAuth(authMethod auth_service.Method) func(*APIContext) {
return func(ctx *APIContext) {
// Get user from session if logged in.
ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
if ctx.User != nil {
if ctx.Locale.Language() != ctx.User.Language {
ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
if ctx.Doer != nil {
if ctx.Locale.Language() != ctx.Doer.Language {
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
}
ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth_service.BasicMethodName
ctx.IsSigned = true
ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
ctx.Data["SignedUser"] = ctx.Doer
ctx.Data["SignedUserID"] = ctx.Doer.ID
ctx.Data["SignedUserName"] = ctx.Doer.Name
ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
} else {
ctx.Data["SignedUserID"] = int64(0)
ctx.Data["SignedUserName"] = ""
@ -269,17 +240,15 @@ func APIAuth(authMethod auth_service.Method) func(*APIContext) {
// APIContexter returns apicontext as middleware
func APIContexter() func(http.Handler) http.Handler {
csrfOpts := getCsrfOpts()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
locale := middleware.Locale(w, req)
ctx := APIContext{
Context: &Context{
Resp: NewResponse(w),
Data: map[string]interface{}{},
Locale: locale,
Session: session.GetSession(req),
Resp: NewResponse(w),
Data: map[string]interface{}{},
Locale: locale,
Cache: cache.GetCache(),
Repo: &Repository{
PullRequest: &PullRequest{},
},
@ -287,9 +256,9 @@ func APIContexter() func(http.Handler) http.Handler {
},
Org: &APIOrganization{},
}
defer ctx.Close()
ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
ctx.csrf = Csrfer(csrfOpts, ctx.Context)
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
@ -301,7 +270,6 @@ func APIContexter() func(http.Handler) http.Handler {
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
ctx.Data["Context"] = &ctx
next.ServeHTTP(ctx.Resp, ctx.Req)
@ -320,36 +288,6 @@ func APIContexter() func(http.Handler) http.Handler {
}
}
// ReferencesGitRepo injects the GitRepo into the Context
func ReferencesGitRepo(allowEmpty bool) func(ctx *APIContext) (cancel context.CancelFunc) {
return func(ctx *APIContext) (cancel context.CancelFunc) {
// Empty repository does not have reference information.
if !allowEmpty && ctx.Repo.Repository.IsEmpty {
return
}
// For API calls.
if ctx.Repo.GitRepo == nil {
repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
gitRepo, err := git.OpenRepositoryCtx(ctx, repoPath)
if err != nil {
ctx.Error(http.StatusInternalServerError, "RepoRef Invalid repo "+repoPath, err)
return
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
return func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}
}
return
}
}
// NotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func (ctx *APIContext) NotFound(objs ...interface{}) {
@ -375,33 +313,63 @@ func (ctx *APIContext) NotFound(objs ...interface{}) {
})
}
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
func RepoRefForAPI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
// ReferencesGitRepo injects the GitRepo into the Context
// you can optional skip the IsEmpty check
func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context.CancelFunc) {
return func(ctx *APIContext) (cancel context.CancelFunc) {
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
if ctx.Repo.Repository.IsEmpty && !(len(allowEmpty) != 0 && allowEmpty[0]) {
return
}
var err error
// For API calls.
if ctx.Repo.GitRepo == nil {
repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
ctx.Repo.GitRepo, err = git.OpenRepositoryCtx(ctx, repoPath)
gitRepo, err := git.OpenRepository(ctx, repoPath)
if err != nil {
ctx.InternalServerError(err)
ctx.Error(http.StatusInternalServerError, "RepoRef Invalid repo "+repoPath, err)
return
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
defer func() {
return func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
}
}
return
}
}
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
func RepoRefForAPI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
if ctx.Repo.GitRepo == nil {
ctx.InternalServerError(fmt.Errorf("no open git repo"))
return
}
if ref := ctx.FormTrim("ref"); len(ref) > 0 {
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetBlobByPath", err)
}
return
}
ctx.Repo.Commit = commit
ctx.Repo.TreePath = ctx.Params("*")
return
}
var err error
refName := getRefName(ctx.Context, RepoRefAny)
if ctx.Repo.GitRepo.IsBranchExist(refName) {

View file

@ -4,12 +4,10 @@
package context
import (
"code.gitea.io/gitea/models"
)
import "code.gitea.io/gitea/models/organization"
// APIOrganization contains organization and team
type APIOrganization struct {
Organization *models.Organization
Team *models.Team
Organization *organization.Organization
Team *organization.Team
}

View file

@ -27,19 +27,19 @@ func Toggle(options *ToggleOptions) func(ctx *Context) {
return func(ctx *Context) {
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(http.StatusOK, "user/auth/activate")
return
}
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
return
}
if ctx.User.MustChangePassword {
if ctx.Doer.MustChangePassword {
if ctx.Req.URL.Path != "/user/settings/change_password" {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
@ -63,7 +63,7 @@ func Toggle(options *ToggleOptions) func(ctx *Context) {
}
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
Validate(ctx, ctx.csrf)
ctx.csrf.Validate(ctx)
if ctx.Written() {
return
}
@ -76,7 +76,7 @@ func Toggle(options *ToggleOptions) func(ctx *Context) {
}
ctx.Redirect(setting.AppSubURL + "/user/login")
return
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
} else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(http.StatusOK, "user/auth/activate")
return
@ -94,7 +94,7 @@ func Toggle(options *ToggleOptions) func(ctx *Context) {
}
if options.AdminRequired {
if !ctx.User.IsAdmin {
if !ctx.Doer.IsAdmin {
ctx.Error(http.StatusForbidden)
return
}
@ -108,15 +108,15 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
return func(ctx *APIContext) {
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "This account is not activated.",
})
return
}
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "This account is prohibited from signing in, please contact your site administrator.",
@ -124,7 +124,7 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
return
}
if ctx.User.MustChangePassword {
if ctx.Doer.MustChangePassword {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
})
@ -145,7 +145,7 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
"message": "Only signed in user is allowed to call APIs.",
})
return
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
} else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(http.StatusOK, "user/auth/activate")
return
@ -154,7 +154,7 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
return // Skip 2FA
}
twofa, err := auth.GetTwoFactorByUID(ctx.User.ID)
twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID)
if err != nil {
if auth.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
@ -178,7 +178,7 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
}
if options.AdminRequired {
if !ctx.User.IsAdmin {
if !ctx.Doer.IsAdmin {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "You have no permission to request for this.",
})

View file

@ -10,6 +10,7 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"html"
"html/template"
"io"
@ -21,6 +22,7 @@ import (
"strings"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
@ -31,13 +33,13 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth"
"gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
chi "github.com/go-chi/chi/v5"
"github.com/unknwon/com"
"github.com/unrolled/render"
"golang.org/x/crypto/pbkdf2"
)
@ -57,18 +59,30 @@ type Context struct {
Render Render
translation.Locale
Cache cache.Cache
csrf CSRF
csrf CSRFProtector
Flash *middleware.Flash
Session session.Store
Link string // current request URL
EscapedLink string
User *user_model.User
Doer *user_model.User
IsSigned bool
IsBasicAuth bool
Repo *Repository
Org *Organization
ContextUser *user_model.User
Repo *Repository
Org *Organization
Package *Package
}
// Close frees all resources hold by Context
func (ctx *Context) Close() error {
var err error
if ctx.Req != nil && ctx.Req.MultipartForm != nil {
err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
}
// TODO: close opened repo, and more
return err
}
// TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString.
@ -88,7 +102,7 @@ func (ctx *Context) GetData() map[string]interface{} {
// IsUserSiteAdmin returns true if current user is a site admin
func (ctx *Context) IsUserSiteAdmin() bool {
return ctx.IsSigned && ctx.User.IsAdmin
return ctx.IsSigned && ctx.Doer.IsAdmin
}
// IsUserRepoOwner returns true if current user owns current repo
@ -139,7 +153,7 @@ func RedirectToUser(ctx *Context, userName string, redirectUserID int64) {
if ctx.Req.URL.RawQuery != "" {
redirectPath += "?" + ctx.Req.URL.RawQuery
}
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
}
// HasAPIError returns true if error occurs in form validation.
@ -181,6 +195,12 @@ func (ctx *Context) RedirectToFirst(location ...string) {
continue
}
// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
// Therefore we should ignore these redirect locations to prevent open redirects
if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
continue
}
u, err := url.Parse(loc)
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
continue
@ -215,7 +235,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
// RenderToString renders the template content to a string
func (ctx *Context) RenderToString(name base.TplName, data map[string]interface{}) (string, error) {
var buf strings.Builder
err := ctx.Render.HTML(&buf, 200, string(name), data)
err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data)
return buf.String(), err
}
@ -269,7 +289,7 @@ func (ctx *Context) ServerError(logMsg string, logErr error) {
func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
if logErr != nil {
log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
if errors.Is(logErr, &net.OpError{}) {
if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
// This is an error within the underlying connection
// and further rendering will not work so just return
return
@ -324,6 +344,18 @@ func (ctx *Context) RespHeader() http.Header {
return ctx.Resp.Header()
}
// SetServeHeaders sets necessary content serve headers
func (ctx *Context) SetServeHeaders(filename string) {
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
}
// ServeContent serves content to http request
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
modTime := time.Now()
@ -333,14 +365,7 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
modTime = v
}
}
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
ctx.SetServeHeaders(name)
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
}
@ -352,31 +377,41 @@ func (ctx *Context) ServeFile(file string, names ...string) {
} else {
name = path.Base(file)
}
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
ctx.SetServeHeaders(name)
http.ServeFile(ctx.Resp, ctx.Req, file)
}
// ServeStream serves file via io stream
func (ctx *Context) ServeStream(rd io.Reader, name string) {
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
ctx.SetServeHeaders(name)
_, err := io.Copy(ctx.Resp, rd)
if err != nil {
ctx.ServerError("Download file failed", err)
}
}
// UploadStream returns the request body or the first form file
// Only form files need to get closed.
func (ctx *Context) UploadStream() (rd io.ReadCloser, needToClose bool, err error) {
contentType := strings.ToLower(ctx.Req.Header.Get("Content-Type"))
if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") || strings.HasPrefix(contentType, "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(32 << 20); err != nil {
return nil, false, err
}
if ctx.Req.MultipartForm.File == nil {
return nil, false, http.ErrMissingFile
}
for _, files := range ctx.Req.MultipartForm.File {
if len(files) > 0 {
r, err := files[0].Open()
return r, true, err
}
}
return nil, false, http.ErrMissingFile
}
return ctx.Req.Body, false, nil
}
// Error returned an error to web browser
func (ctx *Context) Error(status int, contents ...string) {
v := http.StatusText(status)
@ -397,7 +432,7 @@ func (ctx *Context) JSON(status int, content interface{}) {
// Redirect redirects the request
func (ctx *Context) Redirect(location string, status ...int) {
code := http.StatusFound
code := http.StatusSeeOther
if len(status) == 1 {
code = status[0]
}
@ -452,7 +487,7 @@ func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
}
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
text, err = com.AESGCMDecrypt(key, text)
text, err = util.AESGCMDecrypt(key, text)
return string(text), err == nil
}
@ -466,7 +501,7 @@ func (ctx *Context) SetSuperSecureCookie(secret, name, value string, expiry int)
// CookieEncrypt encrypts a given value using the provided secret
func (ctx *Context) CookieEncrypt(secret, value string) string {
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
text, err := com.AESGCMEncrypt(key, []byte(value))
text, err := util.AESGCMEncrypt(key, []byte(value))
if err != nil {
panic("error encrypting cookie: " + err.Error())
}
@ -554,6 +589,22 @@ func (ctx *Context) Value(key interface{}) interface{} {
return ctx.Req.Context().Value(key)
}
// SetTotalCountHeader set "X-Total-Count" header
func (ctx *Context) SetTotalCountHeader(total int64) {
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
}
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) {
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
if len(val) != 0 {
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
} else {
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
}
}
// Handler represents a custom handler
type Handler func(*Context)
@ -574,10 +625,10 @@ func GetContext(req *http.Request) *Context {
// GetContextUser returns context user
func GetContextUser(req *http.Request) *user_model.User {
if apiContext, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
return apiContext.User
return apiContext.Doer
}
if ctx, ok := req.Context().Value(contextKey).(*Context); ok {
return ctx.User
return ctx.Doer
}
return nil
}
@ -599,18 +650,18 @@ func getCsrfOpts() CsrfOptions {
// Auth converts auth.Auth as a middleware
func Auth(authMethod auth.Method) func(*Context) {
return func(ctx *Context) {
ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
if ctx.User != nil {
if ctx.Locale.Language() != ctx.User.Language {
ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
if ctx.Doer != nil {
if ctx.Locale.Language() != ctx.Doer.Language {
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
}
ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth.BasicMethodName
ctx.IsSigned = true
ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
ctx.Data["SignedUser"] = ctx.Doer
ctx.Data["SignedUserID"] = ctx.Doer.ID
ctx.Data["SignedUserName"] = ctx.Doer.Name
ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
} else {
ctx.Data["SignedUserID"] = int64(0)
ctx.Data["SignedUserName"] = ""
@ -625,7 +676,9 @@ func Auth(authMethod auth.Method) func(*Context) {
func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer()
csrfOpts := getCsrfOpts()
if !setting.IsProd {
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
locale := middleware.Locale(resp, req)
@ -650,13 +703,15 @@ func Contexter() func(next http.Handler) http.Handler {
"RunModeIsProd": setting.IsProd,
},
}
defer ctx.Close()
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
ctx.PageData = map[string]interface{}{}
ctx.Data["PageData"] = ctx.PageData
ctx.Data["Context"] = &ctx
ctx.Req = WithContext(req, &ctx)
ctx.csrf = Csrfer(csrfOpts, &ctx)
ctx.csrf = PrepareCSRFProtector(csrfOpts, &ctx)
// Get flash.
flashCookie := ctx.GetCookie("macaron_flash")
@ -714,7 +769,7 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
@ -757,3 +812,21 @@ func Contexter() func(next http.Handler) http.Handler {
})
}
}
// SearchOrderByMap represents all possible search order
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
"asc": {
"alpha": db.SearchOrderByAlphabetically,
"created": db.SearchOrderByOldest,
"updated": db.SearchOrderByLeastUpdated,
"size": db.SearchOrderBySize,
"id": db.SearchOrderByID,
},
"desc": {
"alpha": db.SearchOrderByAlphabeticallyReverse,
"created": db.SearchOrderByNewest,
"updated": db.SearchOrderByRecentUpdated,
"size": db.SearchOrderBySizeReverse,
"id": db.SearchOrderByIDReverse,
},
}

View file

@ -19,38 +19,31 @@
package context
import (
"encoding/base32"
"fmt"
"net/http"
"strconv"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"github.com/unknwon/com"
)
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
type CSRF interface {
// Return HTTP header to search for token.
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
type CSRFProtector interface {
// GetHeaderName returns HTTP header to search for token.
GetHeaderName() string
// Return form value to search for token.
// GetFormName returns form value to search for token.
GetFormName() string
// Return cookie name to search for token.
GetCookieName() string
// Return cookie path
GetCookiePath() string
// Return the flag value used for the csrf token.
GetCookieHTTPOnly() bool
// Return cookie domain
GetCookieDomain() string
// Return the token.
// GetToken returns the token.
GetToken() string
// Validate by token.
ValidToken(t string) bool
// Error replies to the request with a custom function when ValidToken fails.
Error(w http.ResponseWriter)
// Validate validates the token in http context.
Validate(ctx *Context)
}
type csrf struct {
type csrfProtector struct {
// Header name value for setting and getting csrf token.
Header string
// Form name value for setting and getting csrf token.
@ -69,56 +62,24 @@ type csrf struct {
ID string
// Secret used along with the unique id above to generate the Token.
Secret string
// ErrorFunc is the custom function that replies to the request when ValidToken fails.
ErrorFunc func(w http.ResponseWriter)
}
// GetHeaderName returns the name of the HTTP header for csrf token.
func (c *csrf) GetHeaderName() string {
func (c *csrfProtector) GetHeaderName() string {
return c.Header
}
// GetFormName returns the name of the form value for csrf token.
func (c *csrf) GetFormName() string {
func (c *csrfProtector) GetFormName() string {
return c.Form
}
// GetCookieName returns the name of the cookie for csrf token.
func (c *csrf) GetCookieName() string {
return c.Cookie
}
// GetCookiePath returns the path of the cookie for csrf token.
func (c *csrf) GetCookiePath() string {
return c.CookiePath
}
// GetCookieHTTPOnly returns the flag value used for the csrf token.
func (c *csrf) GetCookieHTTPOnly() bool {
return c.CookieHTTPOnly
}
// GetCookieDomain returns the flag value used for the csrf token.
func (c *csrf) GetCookieDomain() string {
return c.CookieDomain
}
// GetToken returns the current token. This is typically used
// to populate a hidden form in an HTML template.
func (c *csrf) GetToken() string {
func (c *csrfProtector) GetToken() string {
return c.Token
}
// ValidToken validates the passed token against the existing Secret and ID.
func (c *csrf) ValidToken(t string) bool {
return ValidToken(t, c.Secret, c.ID, "POST")
}
// Error replies to the request when ValidToken fails.
func (c *csrf) Error(w http.ResponseWriter) {
c.ErrorFunc(w)
}
// CsrfOptions maintains options to manage behavior of Generate.
type CsrfOptions struct {
// The global secret value used to generate Tokens.
@ -140,7 +101,7 @@ type CsrfOptions struct {
SessionKey string
// oldSessionKey saves old value corresponding to SessionKey.
oldSessionKey string
// If true, send token via X-CSRFToken header.
// If true, send token via X-Csrf-Token header.
SetHeader bool
// If true, send token via _csrf cookie.
SetCookie bool
@ -148,52 +109,43 @@ type CsrfOptions struct {
Secure bool
// Disallow Origin appear in request header.
Origin bool
// The function called when Validate fails.
ErrorFunc func(w http.ResponseWriter)
// Cookie life time. Default is 0
// Cookie lifetime. Default is 0
CookieLifeTime int
}
func prepareOptions(options []CsrfOptions) CsrfOptions {
var opt CsrfOptions
if len(options) > 0 {
opt = options[0]
func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions {
if opt.Secret == "" {
randBytes, err := util.CryptoRandomBytes(8)
if err != nil {
// this panic can be handled by the recover() in http handlers
panic(fmt.Errorf("failed to generate random bytes: %w", err))
}
opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
}
// Defaults.
if len(opt.Secret) == 0 {
opt.Secret = string(com.RandomCreateBytes(10))
if opt.Header == "" {
opt.Header = "X-Csrf-Token"
}
if len(opt.Header) == 0 {
opt.Header = "X-CSRFToken"
}
if len(opt.Form) == 0 {
if opt.Form == "" {
opt.Form = "_csrf"
}
if len(opt.Cookie) == 0 {
if opt.Cookie == "" {
opt.Cookie = "_csrf"
}
if len(opt.CookiePath) == 0 {
if opt.CookiePath == "" {
opt.CookiePath = "/"
}
if len(opt.SessionKey) == 0 {
if opt.SessionKey == "" {
opt.SessionKey = "uid"
}
opt.oldSessionKey = "_old_" + opt.SessionKey
if opt.ErrorFunc == nil {
opt.ErrorFunc = func(w http.ResponseWriter) {
http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
}
}
return opt
}
// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
// PrepareCSRFProtector returns a CSRFProtector to be used for every request.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
opt = prepareOptions([]CsrfOptions{opt})
x := &csrf{
func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
opt = prepareDefaultCsrfOptions(opt)
x := &csrfProtector{
Secret: opt.Secret,
Header: opt.Header,
Form: opt.Form,
@ -201,7 +153,6 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
CookieDomain: opt.CookieDomain,
CookiePath: opt.CookiePath,
CookieHTTPOnly: opt.CookieHTTPOnly,
ErrorFunc: opt.ErrorFunc,
}
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
@ -209,33 +160,43 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
}
x.ID = "0"
uid := ctx.Session.Get(opt.SessionKey)
if uid != nil {
x.ID = com.ToStr(uid)
uidAny := ctx.Session.Get(opt.SessionKey)
if uidAny != nil {
switch uidVal := uidAny.(type) {
case string:
x.ID = uidVal
case int64:
x.ID = strconv.FormatInt(uidVal, 10)
default:
log.Error("invalid uid type in session: %T", uidAny)
}
}
needsNew := false
oldUID := ctx.Session.Get(opt.oldSessionKey)
if oldUID == nil || oldUID.(string) != x.ID {
needsNew = true
uidChanged := oldUID == nil || oldUID.(string) != x.ID
cookieToken := ctx.GetCookie(opt.Cookie)
needsNew := true
if uidChanged {
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
} else {
// If cookie present, map existing token, else generate a new one.
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
// FIXME: test coverage.
x.Token = val
} else {
needsNew = true
} else if cookieToken != "" {
// If cookie token presents, re-use existing unexpired token, else generate a new one.
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
x.Token = cookieToken
needsNew = false
}
}
}
if needsNew {
// FIXME: actionId.
x.Token = GenerateToken(x.Secret, x.ID, "POST")
x.Token = GenerateCsrfToken(x.Secret, x.ID, "POST", time.Now())
if opt.SetCookie {
var expires interface{}
if opt.CookieLifeTime == 0 {
expires = time.Now().AddDate(0, 0, 1)
expires = time.Now().Add(CsrfTokenTimeout)
}
middleware.SetCookie(ctx.Resp, opt.Cookie, x.Token,
opt.CookieLifeTime,
@ -255,47 +216,31 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
return x
}
// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
// using ValidToken. If this validation fails, custom Error is sent in the reply.
// If neither a header or form value is found, http.StatusBadRequest is sent.
func Validate(ctx *Context, x CSRF) {
if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
if !x.ValidToken(token) {
// Delete the cookie
middleware.SetCookie(ctx.Resp, x.GetCookieName(), "",
-1,
x.GetCookiePath(),
x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
if middleware.IsAPIPath(ctx.Req) {
x.Error(ctx.Resp)
return
}
func (c *csrfProtector) validateToken(ctx *Context, token string) {
if !ValidCsrfToken(token, c.Secret, c.ID, "POST", time.Now()) {
middleware.DeleteCSRFCookie(ctx.Resp)
if middleware.IsAPIPath(ctx.Req) {
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
} else {
ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
ctx.Redirect(setting.AppSubURL + "/")
}
return
}
if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
if !x.ValidToken(token) {
// Delete the cookie
middleware.SetCookie(ctx.Resp, x.GetCookieName(), "",
-1,
x.GetCookiePath(),
x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
if middleware.IsAPIPath(ctx.Req) {
x.Error(ctx.Resp)
return
}
ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
ctx.Redirect(setting.AppSubURL + "/")
}
return
}
if middleware.IsAPIPath(ctx.Req) {
http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
return
}
ctx.Flash.Error(ctx.Tr("error.missing_csrf"))
ctx.Redirect(setting.AppSubURL + "/")
}
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
// If this validation fails, custom Error is sent in the reply.
// If neither a header nor form value is found, http.StatusBadRequest is sent.
func (c *csrfProtector) Validate(ctx *Context) {
if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" {
c.validateToken(ctx, token)
return
}
if token := ctx.Req.FormValue(c.GetFormName()); token != "" {
c.validateToken(ctx, token)
return
}
c.validateToken(ctx, "") // no csrf token, use an empty token to respond error
}

View file

@ -8,9 +8,10 @@ package context
import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
)
// Organization contains organization context
@ -19,12 +20,12 @@ type Organization struct {
IsMember bool
IsTeamMember bool // Is member of team.
IsTeamAdmin bool // In owner team or team that has admin permission level.
Organization *models.Organization
Organization *organization.Organization
OrgLink string
CanCreateOrgRepo bool
Team *models.Team
Teams []*models.Team
Team *organization.Team
Teams []*organization.Team
}
// HandleOrgAssignment handles organization assignment
@ -51,9 +52,9 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
orgName := ctx.Params(":org")
var err error
ctx.Org.Organization, err = models.GetOrgByName(orgName)
ctx.Org.Organization, err = organization.GetOrgByName(orgName)
if err != nil {
if user_model.IsErrUserNotExist(err) {
if organization.IsErrOrgNotExist(err) {
redirectUserID, err := user_model.LookupUserRedirect(orgName)
if err == nil {
RedirectToUser(ctx, orgName, redirectUserID)
@ -68,23 +69,18 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return
}
org := ctx.Org.Organization
ctx.ContextUser = org.AsUser()
ctx.Data["Org"] = org
teams, err := org.LoadTeams()
if err != nil {
ctx.ServerError("LoadTeams", err)
}
ctx.Data["OrgTeams"] = teams
// Admin has super access.
if ctx.IsSigned && ctx.User.IsAdmin {
if ctx.IsSigned && ctx.Doer.IsAdmin {
ctx.Org.IsOwner = true
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
ctx.Org.CanCreateOrgRepo = true
} else if ctx.IsSigned {
ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.User.ID)
ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsOwnedBy", err)
return
@ -96,12 +92,12 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Org.IsTeamAdmin = true
ctx.Org.CanCreateOrgRepo = true
} else {
ctx.Org.IsMember, err = org.IsOrgMember(ctx.User.ID)
ctx.Org.IsMember, err = org.IsOrgMember(ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsOrgMember", err)
return
}
ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx.User.ID)
ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx.Doer.ID)
if err != nil {
ctx.ServerError("CanCreateOrgRepo", err)
return
@ -118,8 +114,9 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
}
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsPublicMember"] = func(uid int64) bool {
is, _ := models.IsPublicMembership(ctx.Org.Organization.ID, uid)
is, _ := organization.IsPublicMembership(ctx.Org.Organization.ID, uid)
return is
}
ctx.Data["CanCreateOrgRepo"] = ctx.Org.CanCreateOrgRepo
@ -133,7 +130,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
if ctx.Org.IsOwner {
shouldSeeAllTeams = true
} else {
teams, err := org.GetUserTeams(ctx.User.ID)
teams, err := org.GetUserTeams(ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetUserTeams", err)
return
@ -152,7 +149,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return
}
} else {
ctx.Org.Teams, err = org.GetUserTeams(ctx.User.ID)
ctx.Org.Teams, err = org.GetUserTeams(ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetUserTeams", err)
return

110
modules/context/package.go Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
)
// Package contains owner, access mode and optional the package descriptor
type Package struct {
Owner *user_model.User
AccessMode perm.AccessMode
Descriptor *packages_model.PackageDescriptor
}
// PackageAssignment returns a middleware to handle Context.Package assignment
func PackageAssignment() func(ctx *Context) {
return func(ctx *Context) {
packageAssignment(ctx, func(status int, title string, obj interface{}) {
err, ok := obj.(error)
if !ok {
err = fmt.Errorf("%s", obj)
}
if status == http.StatusNotFound {
ctx.NotFound(title, err)
} else {
ctx.ServerError(title, err)
}
})
}
}
// PackageAssignmentAPI returns a middleware to handle Context.Package assignment
func PackageAssignmentAPI() func(ctx *APIContext) {
return func(ctx *APIContext) {
packageAssignment(ctx.Context, ctx.Error)
}
}
func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
ctx.Package = &Package{
Owner: ctx.ContextUser,
}
if ctx.Doer != nil && ctx.Doer.ID == ctx.ContextUser.ID {
ctx.Package.AccessMode = perm.AccessModeOwner
} else {
if ctx.Package.Owner.IsOrganization() {
if organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
ctx.Package.AccessMode = perm.AccessModeRead
if ctx.Doer != nil {
var err error
ctx.Package.AccessMode, err = organization.OrgFromUser(ctx.Package.Owner).GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
if err != nil {
errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", err)
return
}
}
}
} else {
ctx.Package.AccessMode = perm.AccessModeRead
}
}
packageType := ctx.Params("type")
name := ctx.Params("name")
version := ctx.Params("version")
if packageType != "" && name != "" && version != "" {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.Type(packageType), name, version)
if err != nil {
if err == packages_model.ErrPackageNotExist {
errCb(http.StatusNotFound, "GetVersionByNameAndVersion", err)
} else {
errCb(http.StatusInternalServerError, "GetVersionByNameAndVersion", err)
}
return
}
ctx.Package.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv)
if err != nil {
errCb(http.StatusInternalServerError, "GetPackageDescriptor", err)
return
}
}
}
// PackageContexter initializes a package context for a request.
func PackageContexter() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := Context{
Resp: NewResponse(resp),
Data: map[string]interface{}{},
}
defer ctx.Close()
ctx.Req = WithContext(req, &ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}

View file

@ -10,19 +10,19 @@ import (
"net/url"
"strings"
"github.com/unknwon/paginater"
"code.gitea.io/gitea/modules/paginator"
)
// Pagination provides a pagination via Paginater and additional configurations for the link params used in rendering
// Pagination provides a pagination via paginator.Paginator and additional configurations for the link params used in rendering
type Pagination struct {
Paginater *paginater.Paginater
Paginater *paginator.Paginator
urlParams []string
}
// NewPagination creates a new instance of the Pagination struct
func NewPagination(total, page, issueNum, numPages int) *Pagination {
p := &Pagination{}
p.Paginater = paginater.New(total, page, issueNum, numPages)
p.Paginater = paginator.New(total, page, issueNum, numPages)
return p
}
@ -53,5 +53,6 @@ func (p *Pagination) SetDefaultParams(ctx *Context) {
p.AddParam(ctx, "sort", "SortType")
p.AddParam(ctx, "q", "Keyword")
p.AddParam(ctx, "tab", "TabName")
// do not add any more uncommon params here!
p.AddParam(ctx, "t", "queryType")
}

View file

@ -29,6 +29,16 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) {
}
}
// CanEnableEditor checks if the user is allowed to write to the branch of the repo
func CanEnableEditor() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) {
ctx.NotFound("CanWriteToBranch denies permission", nil)
return
}
}
}
// RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission
func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
@ -49,7 +59,7 @@ func RequireRepoReader(unitType unit.Type) func(ctx *Context) {
if ctx.IsSigned {
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
"User in Repo has Permissions: %-+v",
ctx.User,
ctx.Doer,
unitType,
ctx.Repo.Repository,
ctx.Repo.Permission)
@ -80,7 +90,7 @@ func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) {
var args []interface{}
if ctx.IsSigned {
format = "Permission Denied: User %-v cannot read ["
args = append(args, ctx.User)
args = append(args, ctx.Doer)
} else {
format = "Permission Denied: Anonymous user cannot read ["
}

View file

@ -66,6 +66,8 @@ func PrivateContexter() func(http.Handler) http.Handler {
Data: map[string]interface{}{},
},
}
defer ctx.Close()
ctx.Req = WithPrivateContext(req, ctx)
ctx.Data["Context"] = ctx
next.ServeHTTP(ctx.Resp, ctx.Req)
@ -79,6 +81,6 @@ func PrivateContexter() func(http.Handler) http.Handler {
// the underlying request has timed out from the ssh/http push
func OverrideContext(ctx *PrivateContext) (cancel context.CancelFunc) {
// We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
ctx.Override, _, cancel = process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI))
ctx.Override, _, cancel = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
return
}

View file

@ -8,6 +8,7 @@ package context
import (
"context"
"fmt"
"html"
"io"
"net/http"
"net/url"
@ -15,7 +16,6 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@ -30,7 +30,6 @@ import (
asymkey_service "code.gitea.io/gitea/services/asymkey"
"github.com/editorconfig/editorconfig-core-go/v2"
"github.com/unknwon/com"
)
// IssueTemplateDirCandidates issue templates directory
@ -79,8 +78,8 @@ type Repository struct {
}
// CanEnableEditor returns true if repository is editable and user has proper access level.
func (r *Repository) CanEnableEditor() bool {
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch && !r.Repository.IsArchived
func (r *Repository) CanEnableEditor(user *user_model.User) bool {
return r.IsViewBranch && r.Permission.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
}
// CanCreateBranch returns true if repository is editable and user has proper access level.
@ -124,7 +123,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use
sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
canCommit := r.CanEnableEditor() && userCanPush
canCommit := r.CanEnableEditor(doer) && userCanPush
if requireSigned {
canCommit = canCommit && sign
}
@ -140,7 +139,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use
return CanCommitToBranchResults{
CanCommitToBranch: canCommit,
EditorEnabled: r.CanEnableEditor(),
EditorEnabled: r.CanEnableEditor(doer),
UserCanPush: userCanPush,
RequireSigned: requireSigned,
WillSign: sign,
@ -222,13 +221,21 @@ func (r *Repository) FileExists(path, branch string) (bool, error) {
// GetEditorconfig returns the .editorconfig definition if found in the
// HEAD of the default repo branch.
func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
func (r *Repository) GetEditorconfig(optCommit ...*git.Commit) (*editorconfig.Editorconfig, error) {
if r.GitRepo == nil {
return nil, nil
}
commit, err := r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
if err != nil {
return nil, err
var (
err error
commit *git.Commit
)
if len(optCommit) != 0 {
commit = optCommit[0]
} else {
commit, err = r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
if err != nil {
return nil, err
}
}
treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
if err != nil {
@ -256,7 +263,7 @@ func RetrieveBaseRepo(ctx *Context, repo *repo_model.Repository) {
}
ctx.ServerError("GetBaseRepo", err)
return
} else if err = repo.BaseRepo.GetOwner(db.DefaultContext); err != nil {
} else if err = repo.BaseRepo.GetOwner(ctx); err != nil {
ctx.ServerError("BaseRepo.GetOwner", err)
return
}
@ -273,12 +280,12 @@ func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) {
}
ctx.ServerError("GetTemplateRepo", err)
return
} else if err = templateRepo.GetOwner(db.DefaultContext); err != nil {
} else if err = templateRepo.GetOwner(ctx); err != nil {
ctx.ServerError("TemplateRepo.GetOwner", err)
return
}
perm, err := models.GetUserRepoPermission(templateRepo, ctx.User)
perm, err := models.GetUserRepoPermission(ctx, templateRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
@ -309,11 +316,9 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
ctx.PlainText(http.StatusBadRequest, "invalid repository path")
return
}
ctx.PlainText(http.StatusOK, com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
map[string]string{
"GoGetImport": ComposeGoGetImport(username, reponame),
"CloneLink": repo_model.ComposeHTTPSCloneURL(username, reponame),
}))
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), repo_model.ComposeHTTPSCloneURL(username, reponame))
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
ctx.PlainText(http.StatusOK, htmlMeta)
}
// RedirectToRepo redirect to a differently-named repository
@ -336,17 +341,17 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) {
if ctx.Req.URL.RawQuery != "" {
redirectPath += "?" + ctx.Req.URL.RawQuery
}
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
}
func repoAssignment(ctx *Context, repo *repo_model.Repository) {
var err error
if err = repo.GetOwner(db.DefaultContext); err != nil {
if err = repo.GetOwner(ctx); err != nil {
ctx.ServerError("GetOwner", err)
return
}
ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User)
ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
@ -365,15 +370,24 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
ctx.Data["Permission"] = &ctx.Repo.Permission
if repo.IsMirror {
var err error
ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(repo.ID)
// Check if the mirror has finsihed migrationg, only then we can
// lookup the mirror informtation the database.
finishedMigrating, err := models.HasFinishedMigratingTask(repo.ID)
if err != nil {
ctx.ServerError("GetMirrorByRepoID", err)
ctx.ServerError("HasFinishedMigratingTask", err)
return
}
ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
ctx.Data["Mirror"] = ctx.Repo.Mirror
if finishedMigrating {
ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(repo.ID)
if err != nil {
ctx.ServerError("GetMirrorByRepoID", err)
return
}
ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
ctx.Data["Mirror"] = ctx.Repo.Mirror
}
}
pushMirrors, err := repo_model.GetPushMirrorsByRepoID(repo.ID)
@ -410,6 +424,12 @@ func RepoIDAssignment() func(ctx *Context) {
// RepoAssignment returns a middleware to handle repository assignment
func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
log.Trace("RepoAssignment was exec already, skipping second call ...")
return
}
ctx.Data["repoAssignmentExecuted"] = true
var (
owner *user_model.User
err error
@ -418,10 +438,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
userName := ctx.Params(":username")
repoName := ctx.Params(":reponame")
repoName = strings.TrimSuffix(repoName, ".git")
repoName = strings.TrimSuffix(repoName, ".rss")
repoName = strings.TrimSuffix(repoName, ".atom")
// Check if the user is the same as the repository owner
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
owner = ctx.User
if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
owner = ctx.Doer
} else {
owner, err = user_model.GetUserByName(userName)
if err != nil {
@ -438,8 +460,29 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
}
}
ctx.Repo.Owner = owner
ctx.ContextUser = owner
ctx.Data["Username"] = ctx.Repo.Owner.Name
// redirect link to wiki
if strings.HasSuffix(repoName, ".wiki") {
// ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added
// Now we happen to know that all of our paths are: /:username/:reponame/whatever_else
originalRepoName := ctx.Params(":reponame")
redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
redirectPath := strings.Replace(
ctx.Req.URL.EscapedPath(),
url.PathEscape(userName)+"/"+url.PathEscape(originalRepoName),
url.PathEscape(userName)+"/"+url.PathEscape(redirectRepoName)+"/wiki",
1,
)
if ctx.Req.URL.RawQuery != "" {
redirectPath += "?" + ctx.Req.URL.RawQuery
}
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
return
}
// Get repository.
repo, err := repo_model.GetRepositoryByName(owner.ID, repoName)
if err != nil {
@ -500,14 +543,14 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues)
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests)
canSignedUserFork, err := models.CanUserForkRepo(ctx.User, ctx.Repo.Repository)
canSignedUserFork, err := models.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("CanUserForkRepo", err)
return
}
ctx.Data["CanSignedUserFork"] = canSignedUserFork
userAndOrgForks, err := models.GetForksByUserAndOrgs(ctx.User, ctx.Repo.Repository)
userAndOrgForks, err := models.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetForksByUserAndOrgs", err)
return
@ -519,19 +562,26 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
// If multiple forks are available or if the user can fork to another account, but there is already a fork: open selection dialog
ctx.Data["ShowForkModal"] = len(userAndOrgForks) > 1 || (canSignedUserFork && len(userAndOrgForks) > 0)
ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
ctx.Data["RepoCloneLink"] = repo.CloneLink()
cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit
cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous)
if !cloneButtonShowHTTPS && !cloneButtonShowSSH {
// We have to show at least one link, so we just show the HTTPS
cloneButtonShowHTTPS = true
}
ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS
ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH
ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware
ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
if setting.Indexer.RepoIndexerEnabled {
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable()
}
ctx.Data["CloneLink"] = repo.CloneLink()
ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
if ctx.IsSigned {
ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx.User.ID, repo.ID)
ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx.User.ID, repo.ID)
ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx.Doer.ID, repo.ID)
ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx.Doer.ID, repo.ID)
}
if repo.IsFork {
@ -559,7 +609,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
return
}
gitRepo, err := git.OpenRepositoryCtx(ctx, repo_model.RepoPath(userName, repoName))
gitRepo, err := git.OpenRepository(ctx, repo_model.RepoPath(userName, repoName))
if err != nil {
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
@ -575,6 +625,9 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
ctx.ServerError("RepoAssignment Invalid repo "+repo_model.RepoPath(userName, repoName), err)
return
}
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
@ -631,7 +684,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
// People who have push access or have forked repository can propose a new pull request.
canPush := ctx.Repo.CanWrite(unit_model.TypeCode) ||
(ctx.IsSigned && repo_model.HasForkedRepo(ctx.User.ID, ctx.Repo.Repository.ID))
(ctx.IsSigned && repo_model.HasForkedRepo(ctx.Doer.ID, ctx.Repo.Repository.ID))
canCompare := false
// Pull request is allowed if this is a fork repository
@ -667,8 +720,8 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
}
ctx.Data["RepoTransfer"] = repoTransfer
if ctx.User != nil {
ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.User)
if ctx.Doer != nil {
ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.Doer)
}
}
@ -817,7 +870,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if ctx.Repo.GitRepo == nil {
repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
ctx.Repo.GitRepo, err = git.OpenRepositoryCtx(ctx, repoPath)
ctx.Repo.GitRepo, err = git.OpenRepository(ctx, repoPath)
if err != nil {
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return
@ -948,7 +1001,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
// GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.User.CanEditGitHook() {
if !ctx.Doer.CanEditGitHook() {
ctx.NotFound("GitHookService", nil)
return
}
@ -966,6 +1019,7 @@ func UnitTypes() func(ctx *Context) {
ctx.Data["UnitTypeExternalWiki"] = unit_model.TypeExternalWiki
ctx.Data["UnitTypeExternalTracker"] = unit_model.TypeExternalTracker
ctx.Data["UnitTypeProjects"] = unit_model.TypeProjects
ctx.Data["UnitTypePackages"] = unit_model.TypePackages
}
}

56
modules/context/utils.go Normal file
View file

@ -0,0 +1,56 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"net/url"
"strings"
"time"
)
// GetQueryBeforeSince return parsed time (unix format) from URL query's before and since
func GetQueryBeforeSince(ctx *Context) (before, since int64, err error) {
qCreatedBefore, err := prepareQueryArg(ctx, "before")
if err != nil {
return 0, 0, err
}
qCreatedSince, err := prepareQueryArg(ctx, "since")
if err != nil {
return 0, 0, err
}
before, err = parseTime(qCreatedBefore)
if err != nil {
return 0, 0, err
}
since, err = parseTime(qCreatedSince)
if err != nil {
return 0, 0, err
}
return before, since, nil
}
// parseTime parse time and return unix timestamp
func parseTime(value string) (int64, error) {
if len(value) != 0 {
t, err := time.Parse(time.RFC3339, value)
if err != nil {
return 0, err
}
if !t.IsZero() {
return t.Unix(), nil
}
}
return 0, nil
}
// prepareQueryArg unescape and trim a query arg
func prepareQueryArg(ctx *Context, name string) (value string, err error) {
value, err = url.PathUnescape(ctx.FormString(name))
value = strings.TrimSpace(value)
return
}

View file

@ -28,69 +28,69 @@ import (
"time"
)
// Timeout represents the duration that XSRF tokens are valid.
// CsrfTokenTimeout represents the duration that XSRF tokens are valid.
// It is exported so clients may set cookie timeouts that match generated tokens.
const Timeout = 24 * time.Hour
const CsrfTokenTimeout = 24 * time.Hour
// clean sanitizes a string for inclusion in a token by replacing all ":"s.
func clean(s string) string {
return strings.ReplaceAll(s, ":", "_")
}
// CsrfTokenRegenerationInterval is the interval between token generations, old tokens are still valid before CsrfTokenTimeout
var CsrfTokenRegenerationInterval = 10 * time.Minute
// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours.
//
var csrfTokenSep = []byte(":")
// GenerateCsrfToken returns a URL-safe secure XSRF token that expires in CsrfTokenTimeout hours.
// key is a secret key for your application.
// userID is a unique identifier for the user.
// actionID is the action the user is taking (e.g. POSTing to a particular path).
func GenerateToken(key, userID, actionID string) string {
return generateTokenAtTime(key, userID, actionID, time.Now())
}
// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
func GenerateCsrfToken(key, userID, actionID string, now time.Time) string {
nowUnixNano := now.UnixNano()
nowUnixNanoStr := strconv.FormatInt(nowUnixNano, 10)
h := hmac.New(sha1.New, []byte(key))
fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano())
tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano())
h.Write([]byte(strings.ReplaceAll(userID, ":", "_")))
h.Write(csrfTokenSep)
h.Write([]byte(strings.ReplaceAll(actionID, ":", "_")))
h.Write(csrfTokenSep)
h.Write([]byte(nowUnixNanoStr))
tok := fmt.Sprintf("%s:%s", h.Sum(nil), nowUnixNanoStr)
return base64.RawURLEncoding.EncodeToString([]byte(tok))
}
// ValidToken returns true if token is a valid, unexpired token returned by Generate.
func ValidToken(token, key, userID, actionID string) bool {
return validTokenAtTime(token, key, userID, actionID, time.Now())
}
// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
// Decode the token.
func ParseCsrfToken(token string) (issueTime time.Time, ok bool) {
data, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return false
return time.Time{}, false
}
// Extract the issue time of the token.
sep := bytes.LastIndex(data, []byte{':'})
if sep < 0 {
return false
pos := bytes.LastIndex(data, csrfTokenSep)
if pos == -1 {
return time.Time{}, false
}
nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64)
nanos, err := strconv.ParseInt(string(data[pos+1:]), 10, 64)
if err != nil {
return time.Time{}, false
}
return time.Unix(0, nanos), true
}
// ValidCsrfToken returns true if token is a valid and unexpired token returned by Generate.
func ValidCsrfToken(token, key, userID, actionID string, now time.Time) bool {
issueTime, ok := ParseCsrfToken(token)
if !ok {
return false
}
issueTime := time.Unix(0, nanos)
// Check that the token is not expired.
if now.Sub(issueTime) >= Timeout {
if now.Sub(issueTime) >= CsrfTokenTimeout {
return false
}
// Check that the token is not from the future.
// Allow 1 minute grace period in case the token is being verified on a
// Allow 1-minute grace period in case the token is being verified on a
// machine whose clock is behind the machine that issued the token.
if issueTime.After(now.Add(1 * time.Minute)) {
return false
}
expected := generateTokenAtTime(key, userID, actionID, issueTime)
expected := GenerateCsrfToken(key, userID, actionID, issueTime)
// Check that the token matches the expected value.
// Use constant time comparison to avoid timing attacks.

View file

@ -37,18 +37,18 @@ var (
func Test_ValidToken(t *testing.T) {
t.Run("Validate token", func(t *testing.T) {
tok := generateTokenAtTime(key, userID, actionID, now)
assert.True(t, validTokenAtTime(tok, key, userID, actionID, oneMinuteFromNow))
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(Timeout-1*time.Nanosecond)))
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(-1*time.Minute)))
tok := GenerateCsrfToken(key, userID, actionID, now)
assert.True(t, ValidCsrfToken(tok, key, userID, actionID, oneMinuteFromNow))
assert.True(t, ValidCsrfToken(tok, key, userID, actionID, now.Add(CsrfTokenTimeout-1*time.Nanosecond)))
assert.True(t, ValidCsrfToken(tok, key, userID, actionID, now.Add(-1*time.Minute)))
})
}
// Test_SeparatorReplacement tests that separators are being correctly substituted
func Test_SeparatorReplacement(t *testing.T) {
t.Run("Test two separator replacements", func(t *testing.T) {
assert.NotEqual(t, generateTokenAtTime("foo:bar", "baz", "wah", now),
generateTokenAtTime("foo", "bar:baz", "wah", now))
assert.NotEqual(t, GenerateCsrfToken("foo:bar", "baz", "wah", now),
GenerateCsrfToken("foo", "bar:baz", "wah", now))
})
}
@ -61,13 +61,13 @@ func Test_InvalidToken(t *testing.T) {
{"Bad key", "foobar", userID, actionID, oneMinuteFromNow},
{"Bad userID", key, "foobar", actionID, oneMinuteFromNow},
{"Bad actionID", key, userID, "foobar", oneMinuteFromNow},
{"Expired", key, userID, actionID, now.Add(Timeout)},
{"Expired", key, userID, actionID, now.Add(CsrfTokenTimeout)},
{"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)},
}
tok := generateTokenAtTime(key, userID, actionID, now)
tok := GenerateCsrfToken(key, userID, actionID, now)
for _, itt := range invalidTokenTests {
assert.False(t, validTokenAtTime(tok, itt.key, itt.userID, itt.actionID, itt.t))
assert.False(t, ValidCsrfToken(tok, itt.key, itt.userID, itt.actionID, itt.t))
}
})
}
@ -84,7 +84,7 @@ func Test_ValidateBadData(t *testing.T) {
}
for _, bdt := range badDataTests {
assert.False(t, validTokenAtTime(bdt.tok, key, userID, actionID, oneMinuteFromNow))
assert.False(t, ValidCsrfToken(bdt.tok, key, userID, actionID, oneMinuteFromNow))
}
})
}

View file

@ -14,6 +14,8 @@ import (
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
@ -39,12 +41,19 @@ func ToEmail(email *user_model.EmailAddress) *api.Email {
func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
if bp == nil {
var hasPerm bool
var canPush bool
var err error
if user != nil {
hasPerm, err = models.HasAccessUnit(user, repo, unit.TypeCode, perm.AccessModeWrite)
if err != nil {
return nil, err
}
perms, err := models.GetUserRepoPermission(db.DefaultContext, repo, user)
if err != nil {
return nil, err
}
canPush = perms.CanWriteToBranch(user, b.Name)
}
return &api.Branch{
@ -54,7 +63,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod
RequiredApprovals: 0,
EnableStatusCheck: false,
StatusCheckContexts: []string{},
UserCanPush: hasPerm,
UserCanPush: canPush,
UserCanMerge: hasPerm,
}, nil
}
@ -73,12 +82,12 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod
}
if user != nil {
permission, err := models.GetUserRepoPermission(repo, user)
permission, err := models.GetUserRepoPermission(db.DefaultContext, repo, user)
if err != nil {
return nil, err
}
branch.UserCanPush = bp.CanUserPush(user.ID)
branch.UserCanMerge = models.IsUserMergeWhitelisted(bp, user.ID, permission)
branch.UserCanMerge = models.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission)
}
return branch, nil
@ -98,15 +107,15 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection {
if err != nil {
log.Error("GetUserNamesByIDs (ApprovalsWhitelistUserIDs): %v", err)
}
pushWhitelistTeams, err := models.GetTeamNamesByID(bp.WhitelistTeamIDs)
pushWhitelistTeams, err := organization.GetTeamNamesByID(bp.WhitelistTeamIDs)
if err != nil {
log.Error("GetTeamNamesByID (WhitelistTeamIDs): %v", err)
}
mergeWhitelistTeams, err := models.GetTeamNamesByID(bp.MergeWhitelistTeamIDs)
mergeWhitelistTeams, err := organization.GetTeamNamesByID(bp.MergeWhitelistTeamIDs)
if err != nil {
log.Error("GetTeamNamesByID (MergeWhitelistTeamIDs): %v", err)
}
approvalsWhitelistTeams, err := models.GetTeamNamesByID(bp.ApprovalsWhitelistTeamIDs)
approvalsWhitelistTeams, err := organization.GetTeamNamesByID(bp.ApprovalsWhitelistTeamIDs)
if err != nil {
log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err)
}
@ -280,7 +289,7 @@ func ToDeployKey(apiLink string, key *asymkey_model.DeployKey) *api.DeployKey {
}
// ToOrganization convert user_model.User to api.Organization
func ToOrganization(org *models.Organization) *api.Organization {
func ToOrganization(org *organization.Organization) *api.Organization {
return &api.Organization{
ID: org.ID,
AvatarURL: org.AsUser().AvatarLink(),
@ -294,8 +303,8 @@ func ToOrganization(org *models.Organization) *api.Organization {
}
}
// ToTeam convert models.Team to api.Team
func ToTeam(team *models.Team) *api.Team {
// ToTeam convert organization.Team to api.Team
func ToTeam(team *organization.Team) *api.Team {
if team == nil {
return nil
}

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
)
// ToCommitUser convert a git.Signature to an api.CommitUser
@ -146,6 +147,13 @@ func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.
}
}
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
AfterCommitID: commit.ID.String(),
})
if err != nil {
return nil, err
}
return &api.Commit{
CommitMeta: &api.CommitMeta{
URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
@ -175,10 +183,16 @@ func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.
SHA: commit.ID.String(),
Created: commit.Committer.When,
},
Verification: ToVerification(commit),
},
Author: apiAuthor,
Committer: apiCommitter,
Parents: apiParents,
Files: affectedFileList,
Stats: &api.CommitStats{
Total: diff.TotalAddition + diff.TotalDeletion,
Additions: diff.TotalAddition,
Deletions: diff.TotalDeletion,
},
}, nil
}

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
@ -23,13 +24,13 @@ import (
// Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest
func ToAPIIssue(issue *models.Issue) *api.Issue {
if err := issue.LoadLabels(); err != nil {
if err := issue.LoadLabels(db.DefaultContext); err != nil {
return &api.Issue{}
}
if err := issue.LoadPoster(); err != nil {
return &api.Issue{}
}
if err := issue.LoadRepo(); err != nil {
if err := issue.LoadRepo(db.DefaultContext); err != nil {
return &api.Issue{}
}
if err := issue.Repo.GetOwner(db.DefaultContext); err != nil {
@ -214,7 +215,7 @@ func ToLabelList(labels []*models.Label, repo *repo_model.Repository, org *user_
}
// ToAPIMilestone converts Milestone into API Format
func ToAPIMilestone(m *models.Milestone) *api.Milestone {
func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone {
apiMilestone := &api.Milestone{
ID: m.ID,
State: m.State(),

View file

@ -10,6 +10,7 @@ import (
"time"
"code.gitea.io/gitea/models"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
@ -32,7 +33,7 @@ func TestLabel_ToLabel(t *testing.T) {
}
func TestMilestone_APIFormat(t *testing.T) {
milestone := &models.Milestone{
milestone := &issues_model.Milestone{
ID: 3,
RepoID: 4,
Name: "milestoneName",

View file

@ -12,5 +12,7 @@ import (
)
func TestMain(m *testing.M) {
unittest.MainTest(m, filepath.Join("..", ".."))
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", ".."),
})
}

View file

@ -0,0 +1,53 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package convert
import (
"context"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
)
// ToPackage convert a packages.PackageDescriptor to api.Package
func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_model.User) (*api.Package, error) {
var repo *api.Repository
if pd.Repository != nil {
permission, err := models.GetUserRepoPermission(ctx, pd.Repository, doer)
if err != nil {
return nil, err
}
if permission.HasAccess() {
repo = ToRepo(pd.Repository, permission.AccessMode)
}
}
return &api.Package{
ID: pd.Version.ID,
Owner: ToUser(pd.Owner, doer),
Repository: repo,
Creator: ToUser(pd.Creator, doer),
Type: string(pd.Package.Type),
Name: pd.Package.Name,
Version: pd.Version.Version,
CreatedAt: pd.Version.CreatedUnix.AsTime(),
}, nil
}
// ToPackageFile converts packages.PackageFileDescriptor to api.PackageFile
func ToPackageFile(pfd *packages.PackageFileDescriptor) *api.PackageFile {
return &api.PackageFile{
ID: pfd.File.ID,
Size: pfd.Blob.Size,
Name: pfd.File.Name,
HashMD5: pfd.Blob.HashMD5,
HashSHA1: pfd.Blob.HashSHA1,
HashSHA256: pfd.Blob.HashSHA256,
HashSHA512: pfd.Blob.HashSHA512,
}
}

View file

@ -27,23 +27,23 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
err error
)
if err = pr.Issue.LoadRepo(); err != nil {
if err = pr.Issue.LoadRepo(ctx); err != nil {
log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err)
return nil
}
apiIssue := ToAPIIssue(pr.Issue)
if err := pr.LoadBaseRepo(); err != nil {
if err := pr.LoadBaseRepoCtx(ctx); err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
}
if err := pr.LoadHeadRepo(); err != nil {
if err := pr.LoadHeadRepoCtx(ctx); err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
}
p, err := models.GetUserRepoPermission(pr.BaseRepo, doer)
p, err := models.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
if err != nil {
log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
p.AccessMode = perm.AccessModeNone
@ -68,10 +68,13 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
PatchURL: pr.Issue.PatchURL(),
HasMerged: pr.HasMerged,
MergeBase: pr.MergeBase,
Mergeable: pr.Mergeable(),
Deadline: apiIssue.Deadline,
Created: pr.Issue.CreatedUnix.AsTimePtr(),
Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
AllowMaintainerEdit: pr.AllowMaintainerEdit,
Base: &api.PRBranchInfo{
Name: pr.BaseBranch,
Ref: pr.BaseBranch,
@ -85,7 +88,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
},
}
gitRepo, err := git.OpenRepositoryCtx(ctx, pr.BaseRepo.RepoPath())
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
return nil
@ -111,7 +114,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
}
if pr.Flow == models.PullRequestFlowAGit {
gitRepo, err := git.OpenRepositoryCtx(ctx, pr.BaseRepo.RepoPath())
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
log.Error("OpenRepository[%s]: %v", pr.GetGitRefName(), err)
return nil
@ -129,7 +132,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
}
if pr.HeadRepo != nil && pr.Flow == models.PullRequestFlowGithub {
p, err := models.GetUserRepoPermission(pr.HeadRepo, doer)
p, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
if err != nil {
log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
p.AccessMode = perm.AccessModeNone
@ -138,7 +141,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
apiPullRequest.Head.RepoID = pr.HeadRepo.ID
apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, p.AccessMode)
headGitRepo, err := git.OpenRepositoryCtx(ctx, pr.HeadRepo.RepoPath())
headGitRepo, err := git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
if err != nil {
log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), err)
return nil
@ -174,7 +177,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
}
if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
baseGitRepo, err := git.OpenRepositoryCtx(ctx, pr.BaseRepo.RepoPath())
baseGitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
return nil
@ -191,10 +194,6 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
}
}
if pr.Status != models.PullRequestStatusChecking {
mergeable := !(pr.Status == models.PullRequestStatusConflict || pr.Status == models.PullRequestStatusError) && !pr.IsWorkInProgress()
apiPullRequest.Mergeable = mergeable
}
if pr.HasMerged {
apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
apiPullRequest.MergedCommitID = &pr.MergedCommitID

View file

@ -95,3 +95,12 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
DiffViewStyle: user.DiffViewStyle,
}
}
// ToUserAndPermission return User and its collaboration permission for a repository
func ToUserAndPermission(user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission {
return api.RepoCollaboratorPermission{
User: ToUser(user, doer),
Permission: accessMode.String(),
RoleName: accessMode.String(),
}
}

View file

@ -72,8 +72,8 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
"authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"",
fPath,
"gitea admin regenerate keys",
"gitea doctor --run authorized_keys --fix")
return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized_keys --fix"`)
"gitea doctor --run authorized-keys --fix")
return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`)
}
logger.Warn("authorized_keys is out of date. Attempting rewrite...")
err = asymkey_model.RewriteAllPublicKeys()

View file

@ -17,7 +17,7 @@ import (
func checkOldArchives(ctx context.Context, logger log.Logger, autofix bool) error {
numRepos := 0
numReposUpdated := 0
err := iterateRepositories(func(repo *repo_model.Repository) error {
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
if repo.IsEmpty {
return nil
}

View file

@ -268,7 +268,7 @@ func fixBrokenRepoUnits16961(ctx context.Context, logger log.Logger, autofix boo
count := 0
err := db.Iterate(
db.DefaultContext,
ctx,
new(RepoUnit),
builder.Gt{
"id": 0,

View file

@ -18,9 +18,9 @@ import (
"xorm.io/builder"
)
func iteratePRs(repo *repo_model.Repository, each func(*repo_model.Repository, *models.PullRequest) error) error {
func iteratePRs(ctx context.Context, repo *repo_model.Repository, each func(*repo_model.Repository, *models.PullRequest) error) error {
return db.Iterate(
db.DefaultContext,
ctx,
new(models.PullRequest),
builder.Eq{"base_repo_id": repo.ID},
func(idx int, bean interface{}) error {
@ -33,9 +33,9 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro
numRepos := 0
numPRs := 0
numPRsUpdated := 0
err := iterateRepositories(func(repo *repo_model.Repository) error {
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
numRepos++
return iteratePRs(repo, func(repo *repo_model.Repository, pr *models.PullRequest) error {
return iteratePRs(ctx, repo, func(repo *repo_model.Repository, pr *models.PullRequest) error {
numPRs++
pr.BaseRepo = repo
repoPath := repo.RepoPath()
@ -44,17 +44,17 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro
if !pr.HasMerged {
var err error
pr.MergeBase, err = git.NewCommand(ctx, "merge-base", "--", pr.BaseBranch, pr.GetGitRefName()).RunInDir(repoPath)
pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base", "--", pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil {
var err2 error
pr.MergeBase, err2 = git.NewCommand(ctx, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunInDir(repoPath)
pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
if err2 != nil {
logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2)
return nil
}
}
} else {
parentsString, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunInDir(repoPath)
parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil {
logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
return nil
@ -67,7 +67,7 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro
args := append([]string{"merge-base", "--"}, parents[1:]...)
args = append(args, pr.GetGitRefName())
pr.MergeBase, err = git.NewCommand(ctx, args...).RunInDir(repoPath)
pr.MergeBase, _, err = git.NewCommand(ctx, args...).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil {
logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
return nil

View file

@ -27,9 +27,9 @@ import (
"xorm.io/builder"
)
func iterateRepositories(each func(*repo_model.Repository) error) error {
func iterateRepositories(ctx context.Context, each func(*repo_model.Repository) error) error {
err := db.Iterate(
db.DefaultContext,
ctx,
new(repo_model.Repository),
builder.Gt{"id": 0},
func(idx int, bean interface{}) error {
@ -50,7 +50,7 @@ func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error
}
func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
if err := iterateRepositories(func(repo *repo_model.Repository) error {
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
results, err := repository.CheckDelegateHooks(repo.RepoPath())
if err != nil {
logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
@ -86,20 +86,20 @@ func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool
numRepos := 0
numNeedUpdate := 0
if err := iterateRepositories(func(repo *repo_model.Repository) error {
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
numRepos++
r, err := git.OpenRepositoryCtx(git.DefaultContext, repo.RepoPath())
r, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
return err
}
defer r.Close()
if autofix {
_, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunInDir(r.Path)
_, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunStdString(&git.RunOpts{Dir: r.Path})
return err
}
value, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunInDir(r.Path)
value, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunStdString(&git.RunOpts{Dir: r.Path})
if err != nil {
return err
}
@ -132,13 +132,13 @@ func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) err
logger.Critical("Unable to create cache: %v", err)
return err
}
if err := iterateRepositories(func(repo *repo_model.Repository) error {
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
numRepos++
if owner, has := cache.Get(repo.OwnerID); has {
repo.Owner = owner.(*user_model.User)
} else {
if err := repo.GetOwner(db.DefaultContext); err != nil {
if err := repo.GetOwner(ctx); err != nil {
return err
}
cache.Add(repo.OwnerID, repo.Owner)

View file

@ -9,8 +9,11 @@ import (
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
)
@ -25,6 +28,9 @@ func (m *Manager) Init() {
// Run runs the manager within a provided context
func (m *Manager) Run(ctx context.Context) {
ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: EventSource", process.SystemProcessType, true)
defer finished()
then := timeutil.TimeStampNow().Add(-2)
timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime)
loop:
@ -76,6 +82,31 @@ loop:
})
}
then = now
if setting.Service.EnableTimetracking {
usersStopwatches, err := models.GetUIDsAndStopwatch()
if err != nil {
log.Error("Unable to get GetUIDsAndStopwatch: %v", err)
return
}
for _, userStopwatches := range usersStopwatches {
apiSWs, err := convert.ToStopWatches(userStopwatches.StopWatches)
if err != nil {
log.Error("Unable to APIFormat stopwatches: %v", err)
continue
}
dataBs, err := json.Marshal(apiSWs)
if err != nil {
log.Error("Unable to marshal stopwatches: %v", err)
continue
}
m.SendMessage(userStopwatches.UserID, &Event{
Name: "stopwatches",
Data: string(dataBs),
})
}
}
}
}
m.UnregisterAll()

View file

@ -34,10 +34,9 @@ func EnsureValidGitRepository(ctx context.Context, repoPath string) error {
stderr := strings.Builder{}
err := NewCommand(ctx, "rev-parse").
SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repoPath,
Stderr: &stderr,
Run(&RunOpts{
Dir: repoPath,
Stderr: &stderr,
})
if err != nil {
return ConcatenateError(err, (&stderr).String())
@ -58,6 +57,12 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
<-closed
}
// Ensure cancel is called as soon as the provided context is cancelled
go func() {
<-ctx.Done()
cancel()
}()
_, filename, line, _ := runtime.Caller(2)
filename = strings.TrimPrefix(filename, callerPrefix)
@ -65,12 +70,11 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
stderr := strings.Builder{}
err := NewCommand(ctx, "cat-file", "--batch-check").
SetDescription(fmt.Sprintf("%s cat-file --batch-check [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
Stderr: &stderr,
Run(&RunOpts{
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
@ -103,6 +107,12 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
<-closed
}
// Ensure cancel is called as soon as the provided context is cancelled
go func() {
<-ctx.Done()
cancel()
}()
_, filename, line, _ := runtime.Caller(2)
filename = strings.TrimPrefix(filename, callerPrefix)
@ -110,12 +120,11 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
stderr := strings.Builder{}
err := NewCommand(ctx, "cat-file", "--batch").
SetDescription(fmt.Sprintf("%s cat-file --batch [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
Stderr: &stderr,
Run(&RunOpts{
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -17,7 +17,7 @@ import (
func TestBlob_Data(t *testing.T) {
output := "file2\n"
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(bareRepo1Path)
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
if !assert.NoError(t, err) {
t.Fatal()
}
@ -39,7 +39,7 @@ func TestBlob_Data(t *testing.T) {
func Benchmark_Blob_Data(b *testing.B) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(bareRepo1Path)
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
if err != nil {
b.Fatal(err)
}

View file

@ -14,9 +14,11 @@ import (
"os/exec"
"strings"
"time"
"unsafe"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/util"
)
var (
@ -32,10 +34,11 @@ const DefaultLocale = "C"
// Command represents a command with its subcommands or arguments.
type Command struct {
name string
args []string
parentContext context.Context
desc string
name string
args []string
parentContext context.Context
desc string
globalArgsLength int
}
func (c *Command) String() string {
@ -51,9 +54,10 @@ func NewCommand(ctx context.Context, args ...string) *Command {
cargs := make([]string, len(globalCommandArgs))
copy(cargs, globalCommandArgs)
return &Command{
name: GitExecutable,
args: append(cargs, args...),
parentContext: ctx,
name: GitExecutable,
args: append(cargs, args...),
parentContext: ctx,
globalArgsLength: len(globalCommandArgs),
}
}
@ -90,34 +94,8 @@ func (c *Command) AddArguments(args ...string) *Command {
return c
}
// RunInDirTimeoutEnvPipeline executes the command in given directory with given timeout,
// it pipes stdout and stderr to given io.Writer.
func (c *Command) RunInDirTimeoutEnvPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer) error {
return c.RunInDirTimeoutEnvFullPipeline(env, timeout, dir, stdout, stderr, nil)
}
// RunInDirTimeoutEnvFullPipeline executes the command in given directory with given timeout,
// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin.
func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error {
return c.RunInDirTimeoutEnvFullPipelineFunc(env, timeout, dir, stdout, stderr, stdin, nil)
}
// RunInDirTimeoutEnvFullPipelineFunc executes the command in given directory with given timeout,
// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin. Between cmd.Start and cmd.Wait the passed in function is run.
func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader, fn func(context.Context, context.CancelFunc) error) error {
return c.RunWithContext(&RunContext{
Env: env,
Timeout: timeout,
Dir: dir,
Stdout: stdout,
Stderr: stderr,
Stdin: stdin,
PipelineFunc: fn,
})
}
// RunContext represents parameters to run the command
type RunContext struct {
// RunOpts represents parameters to run the command
type RunOpts struct {
Env []string
Timeout time.Duration
Dir string
@ -126,31 +104,48 @@ type RunContext struct {
PipelineFunc func(context.Context, context.CancelFunc) error
}
// RunWithContext run the command with context
func (c *Command) RunWithContext(rc *RunContext) error {
if rc.Timeout == -1 {
rc.Timeout = defaultCommandExecutionTimeout
// Run runs the command with the RunOpts
func (c *Command) Run(opts *RunOpts) error {
if opts == nil {
opts = &RunOpts{}
}
if opts.Timeout <= 0 {
opts.Timeout = defaultCommandExecutionTimeout
}
if len(rc.Dir) == 0 {
if len(opts.Dir) == 0 {
log.Debug("%s", c)
} else {
log.Debug("%s: %v", rc.Dir, c)
log.Debug("%s: %v", opts.Dir, c)
}
desc := c.desc
if desc == "" {
desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(c.args, " "), rc.Dir)
args := c.args[c.globalArgsLength:]
var argSensitiveURLIndexes []int
for i, arg := range c.args {
if strings.Contains(arg, "://") && strings.Contains(arg, "@") {
argSensitiveURLIndexes = append(argSensitiveURLIndexes, i)
}
}
if len(argSensitiveURLIndexes) > 0 {
args = make([]string, len(c.args))
copy(args, c.args)
for _, urlArgIndex := range argSensitiveURLIndexes {
args[urlArgIndex] = util.SanitizeCredentialURLs(args[urlArgIndex])
}
}
desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(args, " "), opts.Dir)
}
ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, rc.Timeout, desc)
ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
defer finished()
cmd := exec.CommandContext(ctx, c.name, c.args...)
if rc.Env == nil {
if opts.Env == nil {
cmd.Env = os.Environ()
} else {
cmd.Env = rc.Env
cmd.Env = opts.Env
}
cmd.Env = append(
@ -162,16 +157,16 @@ func (c *Command) RunWithContext(rc *RunContext) error {
"GIT_NO_REPLACE_OBJECTS=1",
)
cmd.Dir = rc.Dir
cmd.Stdout = rc.Stdout
cmd.Stderr = rc.Stderr
cmd.Stdin = rc.Stdin
cmd.Dir = opts.Dir
cmd.Stdout = opts.Stdout
cmd.Stderr = opts.Stderr
cmd.Stdin = opts.Stdin
if err := cmd.Start(); err != nil {
return err
}
if rc.PipelineFunc != nil {
err := rc.PipelineFunc(ctx, cancel)
if opts.PipelineFunc != nil {
err := opts.PipelineFunc(ctx, cancel)
if err != nil {
cancel()
_ = cmd.Wait()
@ -186,90 +181,69 @@ func (c *Command) RunWithContext(rc *RunContext) error {
return ctx.Err()
}
// RunInDirTimeoutPipeline executes the command in given directory with given timeout,
// it pipes stdout and stderr to given io.Writer.
func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error {
return c.RunInDirTimeoutEnvPipeline(nil, timeout, dir, stdout, stderr)
type RunStdError interface {
error
Stderr() string
}
// RunInDirTimeoutFullPipeline executes the command in given directory with given timeout,
// it pipes stdout and stderr to given io.Writer, and stdin from the given io.Reader
func (c *Command) RunInDirTimeoutFullPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error {
return c.RunInDirTimeoutEnvFullPipeline(nil, timeout, dir, stdout, stderr, stdin)
type runStdError struct {
err error
stderr string
errMsg string
}
// RunInDirTimeout executes the command in given directory with given timeout,
// and returns stdout in []byte and error (combined with stderr).
func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) {
return c.RunInDirTimeoutEnv(nil, timeout, dir)
}
// RunInDirTimeoutEnv executes the command in given directory with given timeout,
// and returns stdout in []byte and error (combined with stderr).
func (c *Command) RunInDirTimeoutEnv(env []string, timeout time.Duration, dir string) ([]byte, error) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
if err := c.RunInDirTimeoutEnvPipeline(env, timeout, dir, stdout, stderr); err != nil {
return nil, ConcatenateError(err, stderr.String())
func (r *runStdError) Error() string {
// the stderr must be in the returned error text, some code only checks `strings.Contains(err.Error(), "git error")`
if r.errMsg == "" {
r.errMsg = ConcatenateError(r.err, r.stderr).Error()
}
if stdout.Len() > 0 && log.IsTrace() {
tracelen := stdout.Len()
if tracelen > 1024 {
tracelen = 1024
}
log.Trace("Stdout:\n %s", stdout.Bytes()[:tracelen])
}
return stdout.Bytes(), nil
return r.errMsg
}
// RunInDirPipeline executes the command in given directory,
// it pipes stdout and stderr to given io.Writer.
func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error {
return c.RunInDirFullPipeline(dir, stdout, stderr, nil)
func (r *runStdError) Unwrap() error {
return r.err
}
// RunInDirFullPipeline executes the command in given directory,
// it pipes stdout and stderr to given io.Writer.
func (c *Command) RunInDirFullPipeline(dir string, stdout, stderr io.Writer, stdin io.Reader) error {
return c.RunInDirTimeoutFullPipeline(-1, dir, stdout, stderr, stdin)
func (r *runStdError) Stderr() string {
return r.stderr
}
// RunInDirBytes executes the command in given directory
// and returns stdout in []byte and error (combined with stderr).
func (c *Command) RunInDirBytes(dir string) ([]byte, error) {
return c.RunInDirTimeout(-1, dir)
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go)
}
// RunInDir executes the command in given directory
// and returns stdout in string and error (combined with stderr).
func (c *Command) RunInDir(dir string) (string, error) {
return c.RunInDirWithEnv(dir, nil)
}
// RunInDirWithEnv executes the command in given directory
// and returns stdout in string and error (combined with stderr).
func (c *Command) RunInDirWithEnv(dir string, env []string) (string, error) {
stdout, err := c.RunInDirTimeoutEnv(env, -1, dir)
// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
stdoutBytes, stderrBytes, err := c.RunStdBytes(opts)
stdout = bytesToString(stdoutBytes)
stderr = bytesToString(stderrBytes)
if err != nil {
return "", err
return stdout, stderr, &runStdError{err: err, stderr: stderr}
}
return string(stdout), nil
// even if there is no err, there could still be some stderr output, so we just return stdout/stderr as they are
return stdout, stderr, nil
}
// RunTimeout executes the command in default working directory with given timeout,
// and returns stdout in string and error (combined with stderr).
func (c *Command) RunTimeout(timeout time.Duration) (string, error) {
stdout, err := c.RunInDirTimeout(timeout, "")
// RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
if opts == nil {
opts = &RunOpts{}
}
if opts.Stdout != nil || opts.Stderr != nil {
// we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug
panic("stdout and stderr field must be nil when using RunStdBytes")
}
stdoutBuf := &bytes.Buffer{}
stderrBuf := &bytes.Buffer{}
opts.Stdout = stdoutBuf
opts.Stderr = stderrBuf
err := c.Run(opts)
stderr = stderrBuf.Bytes()
if err != nil {
return "", err
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
}
return string(stdout), nil
}
// Run executes the command in default working directory
// and returns stdout in string and error (combined with stderr).
func (c *Command) Run() (string, error) {
return c.RunTimeout(-1)
// even if there is no err, there could still be some stderr output
return stdoutBuf.Bytes(), stderr, nil
}
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests

View file

@ -0,0 +1,39 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build race
package git
import (
"context"
"testing"
"time"
)
func TestRunWithContextNoTimeout(t *testing.T) {
maxLoops := 10
// 'git --version' does not block so it must be finished before the timeout triggered.
cmd := NewCommand(context.Background(), "--version")
for i := 0; i < maxLoops; i++ {
if err := cmd.Run(&RunOpts{}); err != nil {
t.Fatal(err)
}
}
}
func TestRunWithContextTimeout(t *testing.T) {
maxLoops := 10
// 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered.
cmd := NewCommand(context.Background(), "hash-object", "--stdin")
for i := 0; i < maxLoops; i++ {
if err := cmd.Run(&RunOpts{Timeout: 1 * time.Millisecond}); err != nil {
if err != context.DeadlineExceeded {
t.Fatalf("Testing %d/%d: %v", i, maxLoops, err)
}
}
}
}

View file

@ -1,40 +1,29 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build race
// +build race
package git
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRunInDirTimeoutPipelineNoTimeout(t *testing.T) {
maxLoops := 1000
// 'git --version' does not block so it must be finished before the timeout triggered.
func TestRunWithContextStd(t *testing.T) {
cmd := NewCommand(context.Background(), "--version")
for i := 0; i < maxLoops; i++ {
if err := cmd.RunInDirTimeoutPipeline(-1, "", nil, nil); err != nil {
t.Fatal(err)
}
}
}
func TestRunInDirTimeoutPipelineAlwaysTimeout(t *testing.T) {
maxLoops := 1000
// 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered.
cmd := NewCommand(context.Background(), "hash-object", "--stdin")
for i := 0; i < maxLoops; i++ {
if err := cmd.RunInDirTimeoutPipeline(1*time.Microsecond, "", nil, nil); err != nil {
if err != context.DeadlineExceeded {
t.Fatalf("Testing %d/%d: %v", i, maxLoops, err)
}
}
stdout, stderr, err := cmd.RunStdString(&RunOpts{})
assert.NoError(t, err)
assert.Empty(t, stderr)
assert.Contains(t, stdout, "git version")
cmd = NewCommand(context.Background(), "--no-such-arg")
stdout, stderr, err = cmd.RunStdString(&RunOpts{})
if assert.Error(t, err) {
assert.Equal(t, stderr, err.Stderr())
assert.Contains(t, err.Stderr(), "unknown option:")
assert.Contains(t, err.Error(), "exit status 129 - unknown option:")
assert.Empty(t, stdout)
}
}

View file

@ -17,6 +17,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
// Commit represents a git commit.
@ -94,7 +95,7 @@ func AddChangesWithArgs(repoPath string, globalArgs []string, all bool, files ..
cmd.AddArguments("--all")
}
cmd.AddArguments("--")
_, err := cmd.AddArguments(files...).RunInDir(repoPath)
_, _, err := cmd.AddArguments(files...).RunStdString(&RunOpts{Dir: repoPath})
return err
}
@ -130,7 +131,7 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt
}
cmd.AddArguments("-m", opts.Message)
_, err := cmd.RunInDir(repoPath)
_, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
// No stderr but exit status 1 means nothing to commit.
if err != nil && err.Error() == "exit status 1" {
return nil
@ -151,7 +152,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file
cmd.AddArguments(files...)
}
stdout, err := cmd.RunInDir(repoPath)
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return 0, err
}
@ -168,7 +169,7 @@ func CommitsCountFiles(ctx context.Context, repoPath string, revision, relpath [
cmd.AddArguments(relpath...)
}
stdout, err := cmd.RunInDir(repoPath)
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return 0, err
}
@ -206,7 +207,7 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
}
if err := CheckGitVersionAtLeast("1.8"); err == nil {
_, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunInDir(c.repo.Path)
_, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path})
if err == nil {
return true, nil
}
@ -219,7 +220,7 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
return false, err
}
result, err := NewCommand(c.repo.Ctx, "rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunInDir(c.repo.Path)
result, _, err := NewCommand(c.repo.Ctx, "rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunStdString(&RunOpts{Dir: c.repo.Path})
if err != nil {
return false, err
}
@ -306,6 +307,35 @@ func (c *Commit) HasFile(filename string) (bool, error) {
return true, nil
}
// GetFileContent reads a file content as a string or returns false if this was not possible
func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
entry, err := c.GetTreeEntryByPath(filename)
if err != nil {
return "", err
}
r, err := entry.Blob().DataAsync()
if err != nil {
return "", err
}
defer r.Close()
if limit > 0 {
bs := make([]byte, limit)
n, err := util.ReadAtMost(r, bs)
if err != nil {
return "", err
}
return string(bs[:n]), nil
}
bytes, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(bytes), nil
}
// GetSubModules get all the sub modules of current revision git tree
func (c *Commit) GetSubModules() (*ObjectCache, error) {
if c.submoduleCache != nil {
@ -381,7 +411,7 @@ func (c *Commit) GetBranchName() (string, error) {
}
args = append(args, "--name-only", "--no-undefined", c.ID.String())
data, err := NewCommand(c.repo.Ctx, args...).RunInDir(c.repo.Path)
data, _, err := NewCommand(c.repo.Ctx, args...).RunStdString(&RunOpts{Dir: c.repo.Path})
if err != nil {
// handle special case where git can not describe commit
if strings.Contains(err.Error(), "cannot describe") {
@ -407,7 +437,7 @@ func (c *Commit) LoadBranchName() (err error) {
// GetTagName gets the current tag name for given commit
func (c *Commit) GetTagName() (string, error) {
data, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always", c.ID.String()).RunInDir(c.repo.Path)
data, _, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always", c.ID.String()).RunStdString(&RunOpts{Dir: c.repo.Path})
if err != nil {
// handle special case where there is no tag for this commit
if strings.Contains(err.Error(), "no tag exactly matches") {
@ -486,11 +516,10 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
stderr := new(bytes.Buffer)
args := []string{"log", "--name-status", "-c", "--pretty=format:", "--parents", "--no-renames", "-z", "-1", commitID}
err := NewCommand(ctx, args...).RunWithContext(&RunContext{
Timeout: -1,
Dir: repoPath,
Stdout: w,
Stderr: stderr,
err := NewCommand(ctx, args...).Run(&RunOpts{
Dir: repoPath,
Stdout: w,
Stderr: stderr,
})
w.Close() // Close writer to exit parsing goroutine
if err != nil {
@ -503,7 +532,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.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, err := NewCommand(ctx, "rev-parse", shortID).RunInDir(repoPath)
commitID, _, err := NewCommand(ctx, "rev-parse", shortID).RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
if strings.Contains(err.Error(), "exit status 128") {
return "", ErrNotExist{shortID, ""}

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -112,7 +112,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
func TestEntries_GetCommitsInfo(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -123,7 +123,7 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
assert.NoError(t, err)
}
defer util.RemoveAll(clonedPath)
clonedRepo1, err := OpenRepository(clonedPath)
clonedRepo1, err := openRepositoryWithDefaultContext(clonedPath)
if err != nil {
assert.NoError(t, err)
}
@ -156,7 +156,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
}
defer util.RemoveAll(repoPath)
if repo, err = OpenRepository(repoPath); err != nil {
if repo, err = openRepositoryWithDefaultContext(repoPath); err != nil {
b.Fatal(err)
}
defer repo.Close()

View file

@ -64,9 +64,10 @@ gpgsig -----BEGIN PGP SIGNATURE-----
empty commit`
sha := SHA1{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
gitRepo, err := OpenRepository(filepath.Join(testReposDir, "repo1_bare"))
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
assert.NoError(t, err)
assert.NotNil(t, gitRepo)
defer gitRepo.Close()
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
assert.NoError(t, err)
@ -109,8 +110,9 @@ empty commit`, commitFromReader.Signature.Payload)
func TestHasPreviousCommit(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(bareRepo1Path)
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer repo.Close()
commit, err := repo.GetCommit("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0")
assert.NoError(t, err)

View file

@ -28,36 +28,24 @@ const (
)
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
return GetRawDiffForFile(ctx, repoPath, "", commitID, diffType, "", writer)
func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer io.Writer) error {
return GetRepoRawDiffForFile(repo, "", commitID, diffType, "", 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 {
stderr := new(bytes.Buffer)
cmd := NewCommand(ctx, "show", "--pretty=format:revert %H%n", "-R", commitID)
if err := cmd.RunWithContext(&RunContext{
Timeout: -1,
Dir: repoPath,
Stdout: writer,
Stderr: stderr,
if err := cmd.Run(&RunOpts{
Dir: repoPath,
Stdout: writer,
Stderr: stderr,
}); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr)
}
return nil
}
// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
func GetRawDiffForFile(ctx context.Context, repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
repo, closer, err := RepositoryFromContextOrOpen(ctx, repoPath)
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
defer closer.Close()
return GetRepoRawDiffForFile(repo, startCommit, endCommit, diffType, file, writer)
}
// GetRepoRawDiffForFile dumps diff results of file in given commit ID to io.Writer according given repository
func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
commit, err := repo.GetCommit(endCommit)
@ -97,11 +85,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
stderr := new(bytes.Buffer)
cmd := NewCommand(repo.Ctx, args...)
if err = cmd.RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: writer,
Stderr: stderr,
if err = cmd.Run(&RunOpts{
Dir: repo.Path,
Stdout: writer,
Stderr: stderr,
}); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr)
}
@ -301,11 +288,10 @@ func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []s
// Run `git diff --name-only` to get the names of the changed files
err = NewCommand(repo.Ctx, "diff", "--name-only", oldCommitID, newCommitID).
RunWithContext(&RunContext{
Env: env,
Timeout: -1,
Dir: repo.Path,
Stdout: stdoutWriter,
Run(&RunOpts{
Env: env,
Dir: repo.Path,
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
// Close the writer end of the pipe to begin processing
_ = stdoutWriter.Close()

View file

@ -0,0 +1,84 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package foreachref
import (
"encoding/hex"
"fmt"
"io"
"strings"
)
var (
nullChar = []byte("\x00")
dualNullChar = []byte("\x00\x00")
)
// Format supports specifying and parsing an output format for 'git
// for-each-ref'. See See git-for-each-ref(1) for available fields.
type Format struct {
// fieldNames hold %(fieldname)s to be passed to the '--format' flag of
// for-each-ref. See git-for-each-ref(1) for available fields.
fieldNames []string
// fieldDelim is the character sequence that is used to separate fields
// for each reference. fieldDelim and refDelim should be selected to not
// interfere with each other and to not be present in field values.
fieldDelim []byte
// fieldDelimStr is a string representation of fieldDelim. Used to save
// us from repetitive reallocation whenever we need the delimiter as a
// string.
fieldDelimStr string
// refDelim is the character sequence used to separate reference from
// each other in the output. fieldDelim and refDelim should be selected
// to not interfere with each other and to not be present in field
// values.
refDelim []byte
}
// NewFormat creates a forEachRefFormat using the specified fieldNames. See
// git-for-each-ref(1) for available fields.
func NewFormat(fieldNames ...string) Format {
return Format{
fieldNames: fieldNames,
fieldDelim: nullChar,
fieldDelimStr: string(nullChar),
refDelim: dualNullChar,
}
}
// Flag returns a for-each-ref --format flag value that captures the fieldNames.
func (f Format) Flag() string {
var formatFlag strings.Builder
for i, field := range f.fieldNames {
// field key and field value
formatFlag.WriteString(fmt.Sprintf("%s %%(%s)", field, field))
if i < len(f.fieldNames)-1 {
// note: escape delimiters to allow control characters as
// delimiters. For example, '%00' for null character or '%0a'
// for newline.
formatFlag.WriteString(f.hexEscaped(f.fieldDelim))
}
}
formatFlag.WriteString(f.hexEscaped(f.refDelim))
return formatFlag.String()
}
// Parser returns a Parser capable of parsing 'git for-each-ref' output produced
// with this Format.
func (f Format) Parser(r io.Reader) *Parser {
return NewParser(r, f)
}
// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0"
// would turn into "%0a%00".
func (f Format) hexEscaped(delim []byte) string {
escaped := ""
for i := 0; i < len(delim); i++ {
escaped += "%" + hex.EncodeToString([]byte{delim[i]})
}
return escaped
}

View file

@ -0,0 +1,67 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package foreachref_test
import (
"testing"
"code.gitea.io/gitea/modules/git/foreachref"
"github.com/stretchr/testify/require"
)
func TestFormat_Flag(t *testing.T) {
tests := []struct {
name string
givenFormat foreachref.Format
wantFlag string
}{
{
name: "references are delimited by dual null chars",
// no reference fields requested
givenFormat: foreachref.NewFormat(),
// only a reference delimiter field in --format
wantFlag: "%00%00",
},
{
name: "a field is a space-separated key-value pair",
givenFormat: foreachref.NewFormat("refname:short"),
// only a reference delimiter field
wantFlag: "refname:short %(refname:short)%00%00",
},
{
name: "fields are separated by a null char field-delimiter",
givenFormat: foreachref.NewFormat("refname:short", "author"),
wantFlag: "refname:short %(refname:short)%00author %(author)%00%00",
},
{
name: "multiple fields",
givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"),
wantFlag: "refname:short %(refname:short)%00objecttype %(objecttype)%00objectname %(objectname)%00%00",
},
}
for _, test := range tests {
tc := test // don't close over loop variable
t.Run(tc.name, func(t *testing.T) {
gotFlag := tc.givenFormat.Flag()
require.Equal(t, tc.wantFlag, gotFlag, "unexpected for-each-ref --format string. wanted: '%s', got: '%s'", tc.wantFlag, gotFlag)
})
}
}

View file

@ -0,0 +1,131 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package foreachref
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
// Parser parses 'git for-each-ref' output according to a given output Format.
type Parser struct {
// tokenizes 'git for-each-ref' output into "reference paragraphs".
scanner *bufio.Scanner
// format represents the '--format' string that describes the expected
// 'git for-each-ref' output structure.
format Format
// err holds the last encountered error during parsing.
err error
}
// NewParser creates a 'git for-each-ref' output parser that will parse all
// references in the provided Reader. The references in the output are assumed
// to follow the specified Format.
func NewParser(r io.Reader, format Format) *Parser {
scanner := bufio.NewScanner(r)
// in addition to the reference delimiter we specified in the --format,
// `git for-each-ref` will always add a newline after every reference.
refDelim := make([]byte, 0, len(format.refDelim)+1)
refDelim = append(refDelim, format.refDelim...)
refDelim = append(refDelim, '\n')
// Split input into delimiter-separated "reference blocks".
scanner.Split(
func(data []byte, atEOF bool) (advance int, token []byte, err error) {
// Scan until delimiter, marking end of reference.
delimIdx := bytes.Index(data, refDelim)
if delimIdx >= 0 {
token := data[:delimIdx]
advance := delimIdx + len(refDelim)
return advance, token, nil
}
// If we're at EOF, we have a final, non-terminated reference. Return it.
if atEOF {
return len(data), data, nil
}
// Not yet a full field. Request more data.
return 0, nil, nil
})
return &Parser{
scanner: scanner,
format: format,
err: nil,
}
}
// Next returns the next reference as a collection of key-value pairs. nil
// denotes EOF but is also returned on errors. The Err method should always be
// consulted after Next returning nil.
//
// It could, for example return something like:
//
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
//
func (p *Parser) Next() map[string]string {
if !p.scanner.Scan() {
return nil
}
fields, err := p.parseRef(p.scanner.Text())
if err != nil {
p.err = err
return nil
}
return fields
}
// Err returns the latest encountered parsing error.
func (p *Parser) Err() error {
return p.err
}
// parseRef parses out all key-value pairs from a single reference block, such as
//
// "objecttype tag\0refname:short v1.16.4\0object f460b7543ed500e49c133c2cd85c8c55ee9dbe27"
//
func (p *Parser) parseRef(refBlock string) (map[string]string, error) {
if refBlock == "" {
// must be at EOF
return nil, nil
}
fieldValues := make(map[string]string)
fields := strings.Split(refBlock, p.format.fieldDelimStr)
if len(fields) != len(p.format.fieldNames) {
return nil, fmt.Errorf("unexpected number of reference fields: wanted %d, was %d",
len(fields), len(p.format.fieldNames))
}
for i, field := range fields {
field = strings.TrimSpace(field)
var fieldKey string
var fieldVal string
firstSpace := strings.Index(field, " ")
if firstSpace > 0 {
fieldKey = field[:firstSpace]
fieldVal = field[firstSpace+1:]
} else {
// could be the case if the requested field had no value
fieldKey = field
}
// enforce the format order of fields
if p.format.fieldNames[i] != fieldKey {
return nil, fmt.Errorf("unexpected field name at position %d: wanted: '%s', was: '%s'",
i, p.format.fieldNames[i], fieldKey)
}
fieldValues[fieldKey] = fieldVal
}
return fieldValues, nil
}

View file

@ -0,0 +1,228 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package foreachref_test
import (
"errors"
"fmt"
"io"
"strings"
"testing"
"code.gitea.io/gitea/modules/git/foreachref"
"code.gitea.io/gitea/modules/json"
"github.com/stretchr/testify/require"
)
type refSlice = []map[string]string
func TestParser(t *testing.T) {
tests := []struct {
name string
givenFormat foreachref.Format
givenInput io.Reader
wantRefs refSlice
wantErr bool
expectedErr error
}{
// this would, for example, be the result when running `git
// for-each-ref refs/tags` on a repo without tags.
{
name: "no references on empty input",
givenFormat: foreachref.NewFormat("refname:short"),
givenInput: strings.NewReader(``),
wantRefs: []map[string]string{},
},
// note: `git for-each-ref` will add a newline between every
// reference (in addition to the ref-delimiter we've chosen)
{
name: "single field requested, single reference in output",
givenFormat: foreachref.NewFormat("refname:short"),
givenInput: strings.NewReader("refname:short v0.0.1\x00\x00" + "\n"),
wantRefs: []map[string]string{
{"refname:short": "v0.0.1"},
},
},
{
name: "single field requested, multiple references in output",
givenFormat: foreachref.NewFormat("refname:short"),
givenInput: strings.NewReader(
"refname:short v0.0.1\x00\x00" + "\n" +
"refname:short v0.0.2\x00\x00" + "\n" +
"refname:short v0.0.3\x00\x00" + "\n"),
wantRefs: []map[string]string{
{"refname:short": "v0.0.1"},
{"refname:short": "v0.0.2"},
{"refname:short": "v0.0.3"},
},
},
{
name: "multiple fields requested for each reference",
givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"),
givenInput: strings.NewReader(
"refname:short v0.0.1\x00objecttype commit\x00objectname 7b2c5ac9fc04fc5efafb60700713d4fa609b777b\x00\x00" + "\n" +
"refname:short v0.0.2\x00objecttype commit\x00objectname a1f051bc3eba734da4772d60e2d677f47cf93ef4\x00\x00" + "\n" +
"refname:short v0.0.3\x00objecttype commit\x00objectname ef82de70bb3f60c65fb8eebacbb2d122ef517385\x00\x00" + "\n",
),
wantRefs: []map[string]string{
{
"refname:short": "v0.0.1",
"objecttype": "commit",
"objectname": "7b2c5ac9fc04fc5efafb60700713d4fa609b777b",
},
{
"refname:short": "v0.0.2",
"objecttype": "commit",
"objectname": "a1f051bc3eba734da4772d60e2d677f47cf93ef4",
},
{
"refname:short": "v0.0.3",
"objecttype": "commit",
"objectname": "ef82de70bb3f60c65fb8eebacbb2d122ef517385",
},
},
},
{
name: "must handle multi-line fields such as 'content'",
givenFormat: foreachref.NewFormat("refname:short", "contents", "author"),
givenInput: strings.NewReader(
"refname:short v0.0.1\x00contents Create new buffer if not present yet (#549)\n\nFixes a nil dereference when ProcessFoo is used\nwith multiple commands.\x00author Foo Bar <foo@bar.com> 1507832733 +0200\x00\x00" + "\n" +
"refname:short v0.0.2\x00contents Update CI config (#651)\n\n\x00author John Doe <john.doe@foo.com> 1521643174 +0000\x00\x00" + "\n" +
"refname:short v0.0.3\x00contents Fixed code sample for bash completion (#687)\n\n\x00author Foo Baz <foo@baz.com> 1524836750 +0200\x00\x00" + "\n",
),
wantRefs: []map[string]string{
{
"refname:short": "v0.0.1",
"contents": "Create new buffer if not present yet (#549)\n\nFixes a nil dereference when ProcessFoo is used\nwith multiple commands.",
"author": "Foo Bar <foo@bar.com> 1507832733 +0200",
},
{
"refname:short": "v0.0.2",
"contents": "Update CI config (#651)",
"author": "John Doe <john.doe@foo.com> 1521643174 +0000",
},
{
"refname:short": "v0.0.3",
"contents": "Fixed code sample for bash completion (#687)",
"author": "Foo Baz <foo@baz.com> 1524836750 +0200",
},
},
},
{
name: "must handle fields without values",
givenFormat: foreachref.NewFormat("refname:short", "object", "objecttype"),
givenInput: strings.NewReader(
"refname:short v0.0.1\x00object \x00objecttype commit\x00\x00" + "\n" +
"refname:short v0.0.2\x00object \x00objecttype commit\x00\x00" + "\n" +
"refname:short v0.0.3\x00object \x00objecttype commit\x00\x00" + "\n",
),
wantRefs: []map[string]string{
{
"refname:short": "v0.0.1",
"object": "",
"objecttype": "commit",
},
{
"refname:short": "v0.0.2",
"object": "",
"objecttype": "commit",
},
{
"refname:short": "v0.0.3",
"object": "",
"objecttype": "commit",
},
},
},
{
name: "must fail when the number of fields in the input doesn't match expected format",
givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"),
givenInput: strings.NewReader(
"refname:short v0.0.1\x00objecttype commit\x00\x00" + "\n" +
"refname:short v0.0.2\x00objecttype commit\x00\x00" + "\n" +
"refname:short v0.0.3\x00objecttype commit\x00\x00" + "\n",
),
wantErr: true,
expectedErr: errors.New("unexpected number of reference fields: wanted 2, was 3"),
},
{
name: "must fail input fields don't match expected format",
givenFormat: foreachref.NewFormat("refname:short", "objectname"),
givenInput: strings.NewReader(
"refname:short v0.0.1\x00objecttype commit\x00\x00" + "\n" +
"refname:short v0.0.2\x00objecttype commit\x00\x00" + "\n" +
"refname:short v0.0.3\x00objecttype commit\x00\x00" + "\n",
),
wantErr: true,
expectedErr: errors.New("unexpected field name at position 1: wanted: 'objectname', was: 'objecttype'"),
},
}
for _, test := range tests {
tc := test // don't close over loop variable
t.Run(tc.name, func(t *testing.T) {
parser := tc.givenFormat.Parser(tc.givenInput)
//
// parse references from input
//
gotRefs := make([]map[string]string, 0)
for {
ref := parser.Next()
if ref == nil {
break
}
gotRefs = append(gotRefs, ref)
}
err := parser.Err()
//
// verify expectations
//
if tc.wantErr {
require.Error(t, err)
require.EqualError(t, err, tc.expectedErr.Error())
} else {
require.NoError(t, err, "for-each-ref parser unexpectedly failed with: %v", err)
require.Equal(t, tc.wantRefs, gotRefs, "for-each-ref parser produced unexpected reference set. wanted: %v, got: %v", pretty(tc.wantRefs), pretty(gotRefs))
}
})
}
}
func pretty(v interface{}) string {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
// shouldn't happen
panic(fmt.Sprintf("json-marshalling failed: %v", err))
}
return string(data)
}

View file

@ -8,6 +8,7 @@ package git
import (
"context"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
@ -20,10 +21,11 @@ import (
)
var (
// Prefix the log prefix
Prefix = "[git-module] "
// GitVersionRequired is the minimum Git version required
GitVersionRequired = "1.7.2"
// At the moment, all code for git 1.x are not changed, if some users want to test with old git client
// or bypass the check, they still have a chance to edit this variable manually.
// If everything works fine, the code for git 1.x could be removed in a separate PR before 1.17 frozen.
GitVersionRequired = "2.0.0"
// GitExecutable is the command name of git
// Could be updated to an absolute path while initialization
@ -54,9 +56,9 @@ func LoadGitVersion() error {
return nil
}
stdout, err := NewCommand(context.Background(), "version").Run()
if err != nil {
return err
stdout, _, runErr := NewCommand(context.Background(), "version").RunStdString(nil)
if runErr != nil {
return runErr
}
fields := strings.Fields(stdout)
@ -74,6 +76,7 @@ func LoadGitVersion() error {
versionString = fields[2]
}
var err error
gitVersion, err = version.NewVersion(versionString)
return err
}
@ -86,13 +89,13 @@ func SetExecutablePath(path string) error {
}
absPath, err := exec.LookPath(GitExecutable)
if err != nil {
return fmt.Errorf("Git not found: %v", err)
return fmt.Errorf("git not found: %w", err)
}
GitExecutable = absPath
err = LoadGitVersion()
if err != nil {
return fmt.Errorf("Git version missing: %v", err)
return fmt.Errorf("unable to load git version: %w", err)
}
versionRequired, err := version.NewVersion(GitVersionRequired)
@ -101,7 +104,15 @@ func SetExecutablePath(path string) error {
}
if gitVersion.LessThan(versionRequired) {
return fmt.Errorf("Git version not supported. Requires version > %v", GitVersionRequired)
moreHint := "get git: https://git-scm.com/download/"
if runtime.GOOS == "linux" {
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
if _, err = os.Stat("/etc/redhat-release"); err == nil {
// ius.io is the recommended official(git-scm.com) method to install git
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
}
}
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), GitVersionRequired, moreHint)
}
return nil
@ -124,7 +135,9 @@ func VersionInfo() string {
func Init(ctx context.Context) error {
DefaultContext = ctx
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
if setting.Git.Timeout.Default > 0 {
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
}
if err := SetExecutablePath(setting.Git.Path); err != nil {
return err
@ -145,7 +158,7 @@ func Init(ctx context.Context) error {
// By default partial clones are disabled, enable them from git v2.22
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true")
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
}
// Save current git version on init to gitVersion otherwise it would require an RWMutex
@ -295,10 +308,5 @@ func checkAndRemoveConfig(key, value string) error {
// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
// Make sure timeout makes sense.
if timeout <= 0 {
timeout = -1
}
_, err := NewCommand(ctx, "fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
return err
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
}

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -55,11 +55,10 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
go func() {
stderr := strings.Builder{}
err := NewCommand(ctx, args...).RunWithContext(&RunContext{
Timeout: -1,
Dir: repository,
Stdout: stdoutWriter,
Stderr: &stderr,
err := NewCommand(ctx, args...).Run(&RunOpts{
Dir: repository,
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -14,7 +14,7 @@ import (
func TestGetNotes(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -27,7 +27,7 @@ func TestGetNotes(t *testing.T) {
func TestGetNestedNotes(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo3_notes")
repo, err := OpenRepository(repoPath)
repo, err := openRepositoryWithDefaultContext(repoPath)
assert.NoError(t, err)
defer repo.Close()
@ -42,7 +42,7 @@ func TestGetNestedNotes(t *testing.T) {
func TestGetNonExistentNotes(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -27,12 +27,11 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand(ctx, "cat-file", "--batch-check")
if err := cmd.RunWithContext(&git.RunContext{
Timeout: -1,
Dir: tmpBasePath,
Stdin: shasToCheckReader,
Stdout: catFileCheckWriter,
Stderr: stderr,
if err := cmd.Run(&git.RunOpts{
Dir: tmpBasePath,
Stdin: shasToCheckReader,
Stdout: catFileCheckWriter,
Stderr: stderr,
}); err != nil {
_ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check [%s]: %v - %s", tmpBasePath, err, errbuf.String()))
}
@ -46,11 +45,10 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand(ctx, "cat-file", "--batch-check", "--batch-all-objects")
if err := cmd.RunWithContext(&git.RunContext{
Timeout: -1,
Dir: tmpBasePath,
Stdout: catFileCheckWriter,
Stderr: stderr,
if err := cmd.Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: catFileCheckWriter,
Stderr: stderr,
}); err != nil {
log.Error("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String())
err = fmt.Errorf("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String())
@ -67,12 +65,11 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile
stderr := new(bytes.Buffer)
var errbuf strings.Builder
if err := git.NewCommand(ctx, "cat-file", "--batch").RunWithContext(&git.RunContext{
Timeout: -1,
Dir: tmpBasePath,
Stdout: catFileBatchWriter,
Stdin: shasToBatchReader,
Stderr: stderr,
if err := git.NewCommand(ctx, "cat-file", "--batch").Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: catFileBatchWriter,
Stdin: shasToBatchReader,
Stderr: stderr,
}); err != nil {
_ = shasToBatchReader.CloseWithError(fmt.Errorf("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String()))
}

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package pipeline

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package pipeline
@ -53,11 +52,10 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
go func() {
stderr := strings.Builder{}
err := git.NewCommand(repo.Ctx, "rev-list", "--all").RunWithContext(&git.RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: revListWriter,
Stderr: &stderr,
err := git.NewCommand(repo.Ctx, "rev-list", "--all").Run(&git.RunOpts{
Dir: repo.Path,
Stdout: revListWriter,
Stderr: &stderr,
})
if err != nil {
_ = revListWriter.CloseWithError(git.ConcatenateError(err, (&stderr).String()))

View file

@ -23,12 +23,11 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS
stderr := new(bytes.Buffer)
var errbuf strings.Builder
if err := git.NewCommand(ctx, "name-rev", "--stdin", "--name-only", "--always").RunWithContext(&git.RunContext{
Timeout: -1,
Dir: tmpBasePath,
Stdout: nameRevStdinWriter,
Stdin: shasToNameReader,
Stderr: stderr,
if err := git.NewCommand(ctx, "name-rev", "--stdin", "--name-only", "--always").Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: nameRevStdinWriter,
Stdin: shasToNameReader,
Stderr: stderr,
}); err != nil {
_ = shasToNameReader.CloseWithError(fmt.Errorf("git name-rev [%s]: %v - %s", tmpBasePath, err, errbuf.String()))
}

View file

@ -25,11 +25,10 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand(ctx, "rev-list", "--objects", "--all")
if err := cmd.RunWithContext(&git.RunContext{
Timeout: -1,
Dir: basePath,
Stdout: revListWriter,
Stderr: stderr,
if err := cmd.Run(&git.RunOpts{
Dir: basePath,
Stdout: revListWriter,
Stderr: stderr,
}); err != nil {
log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String())
err = fmt.Errorf("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String())
@ -45,11 +44,10 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand(ctx, "rev-list", "--objects", headSHA, "--not", baseSHA)
if err := cmd.RunWithContext(&git.RunContext{
Timeout: -1,
Dir: tmpBasePath,
Stdout: revListWriter,
Stderr: stderr,
if err := cmd.Run(&git.RunOpts{
Dir: tmpBasePath,
Stdout: revListWriter,
Stderr: stderr,
}); err != nil {
log.Error("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String())
errChan <- fmt.Errorf("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String())

View file

@ -22,7 +22,7 @@ func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.UR
cmd = NewCommand(ctx, "config", "--get", "remote."+remoteName+".url")
}
result, err := cmd.RunInDir(repoPath)
result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return nil, err
}

View file

@ -19,6 +19,7 @@ import (
"time"
"code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/util"
)
// GPGSettings represents the default GPG settings for this repository
@ -58,7 +59,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
// IsRepoURLAccessible checks if given repository URL is accessible.
func IsRepoURLAccessible(ctx context.Context, url string) bool {
_, err := NewCommand(ctx, "ls-remote", "-q", "-h", url, "HEAD").Run()
_, _, err := NewCommand(ctx, "ls-remote", "-q", "-h", url, "HEAD").RunStdString(nil)
return err == nil
}
@ -73,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
if bare {
cmd.AddArguments("--bare")
}
_, err = cmd.RunInDir(repoPath)
_, _, err = cmd.RunStdString(&RunOpts{Dir: repoPath})
return err
}
@ -81,11 +82,10 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder
if err := NewCommand(repo.Ctx, "show-ref", "--head", "^HEAD$").
RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: &output,
Stderr: &errbuf,
Run(&RunOpts{
Dir: repo.Path,
Stdout: &output,
Stderr: &errbuf,
}); err != nil {
if err.Error() == "exit status 1" && errbuf.String() == "" {
return true, nil
@ -98,15 +98,16 @@ func (repo *Repository) IsEmpty() (bool, error) {
// CloneRepoOptions options when clone a repository
type CloneRepoOptions struct {
Timeout time.Duration
Mirror bool
Bare bool
Quiet bool
Branch string
Shared bool
NoCheckout bool
Depth int
Filter string
Timeout time.Duration
Mirror bool
Bare bool
Quiet bool
Branch string
Shared bool
NoCheckout bool
Depth int
Filter string
SkipTLSVerify bool
}
// Clone clones original repository to target path.
@ -124,6 +125,9 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
}
cmd := NewCommandContextNoGlobals(ctx, args...).AddArguments("clone")
if opts.SkipTLSVerify {
cmd.AddArguments("-c", "http.sslVerify=false")
}
if opts.Mirror {
cmd.AddArguments("--mirror")
}
@ -150,6 +154,12 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
}
cmd.AddArguments("--", from, to)
if strings.Contains(from, "://") && strings.Contains(from, "@") {
cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, util.SanitizeCredentialURLs(from), to, opts.Shared, opts.Mirror, opts.Depth))
} else {
cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, from, to, opts.Shared, opts.Mirror, opts.Depth))
}
if opts.Timeout <= 0 {
opts.Timeout = -1
}
@ -163,7 +173,7 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
}
stderr := new(bytes.Buffer)
if err = cmd.RunWithContext(&RunContext{
if err = cmd.Run(&RunOpts{
Timeout: opts.Timeout,
Env: envs,
Stdout: io.Discard,
@ -197,13 +207,18 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
if len(opts.Branch) > 0 {
cmd.AddArguments(opts.Branch)
}
if strings.Contains(opts.Remote, "://") && strings.Contains(opts.Remote, "@") {
cmd.SetDescription(fmt.Sprintf("push branch %s to %s (force: %t, mirror: %t)", opts.Branch, util.SanitizeCredentialURLs(opts.Remote), opts.Force, opts.Mirror))
} else {
cmd.SetDescription(fmt.Sprintf("push branch %s to %s (force: %t, mirror: %t)", opts.Branch, opts.Remote, opts.Force, opts.Mirror))
}
var outbuf, errbuf strings.Builder
if opts.Timeout == 0 {
opts.Timeout = -1
}
err := cmd.RunWithContext(&RunContext{
err := cmd.Run(&RunOpts{
Env: opts.Env,
Timeout: opts.Timeout,
Dir: repoPath,
@ -245,7 +260,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
// GetLatestCommitTime returns time for latest commit in repository (across all branches)
func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) {
cmd := NewCommand(ctx, "for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)")
stdout, err := cmd.RunInDir(repoPath)
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return time.Time{}, err
}
@ -262,7 +277,7 @@ type DivergeObject struct {
func checkDivergence(ctx context.Context, repoPath, baseBranch, targetBranch string) (int, error) {
branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch)
cmd := NewCommand(ctx, "rev-list", "--count", branches)
stdout, err := cmd.RunInDir(repoPath)
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return -1, err
}
@ -299,23 +314,23 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.
defer os.RemoveAll(tmp)
env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects"))
_, err = NewCommand(ctx, "init", "--bare").RunInDirWithEnv(tmp, env)
_, _, err = NewCommand(ctx, "init", "--bare").RunStdString(&RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
_, err = NewCommand(ctx, "reset", "--soft", commit).RunInDirWithEnv(tmp, env)
_, _, err = NewCommand(ctx, "reset", "--soft", commit).RunStdString(&RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
_, err = NewCommand(ctx, "branch", "-m", "bundle").RunInDirWithEnv(tmp, env)
_, _, err = NewCommand(ctx, "branch", "-m", "bundle").RunStdString(&RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
tmpFile := filepath.Join(tmp, "bundle")
_, err = NewCommand(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunInDirWithEnv(tmp, env)
_, _, err = NewCommand(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}

View file

@ -57,11 +57,10 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
)
var stderr strings.Builder
err := NewCommand(ctx, args...).RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: target,
Stderr: &stderr,
err := NewCommand(ctx, args...).Run(&RunOpts{
Dir: repo.Path,
Stdout: target,
Stderr: &stderr,
})
if err != nil {
return ConcatenateError(err, stderr.String())

View file

@ -76,12 +76,11 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
cmd := NewCommand(repo.Ctx, cmdArgs...)
if err := cmd.RunWithContext(&RunContext{
Env: env,
Timeout: -1,
Dir: repo.Path,
Stdout: stdOut,
Stderr: stdErr,
if err := cmd.Run(&RunOpts{
Env: env,
Dir: repo.Path,
Stdout: stdOut,
Stderr: stdErr,
}); err != nil {
return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())
}
@ -125,12 +124,10 @@ type CheckAttributeReader struct {
env []string
ctx context.Context
cancel context.CancelFunc
running chan struct{}
}
// Init initializes the cmd
func (c *CheckAttributeReader) Init(ctx context.Context) error {
c.running = make(chan struct{})
cmdArgs := []string{"check-attr", "--stdin", "-z"}
if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
@ -189,21 +186,12 @@ func (c *CheckAttributeReader) Run() error {
_ = c.stdOut.Close()
}()
stdErr := new(bytes.Buffer)
err := c.cmd.RunWithContext(&RunContext{
Env: c.env,
Timeout: -1,
Dir: c.Repo.Path,
Stdin: c.stdinReader,
Stdout: c.stdOut,
Stderr: stdErr,
PipelineFunc: func(_ context.Context, _ context.CancelFunc) error {
select {
case <-c.running:
default:
close(c.running)
}
return nil
},
err := c.cmd.Run(&RunOpts{
Env: c.env,
Dir: c.Repo.Path,
Stdin: c.stdinReader,
Stdout: c.stdOut,
Stderr: stdErr,
})
if err != nil && // If there is an error we need to return but:
c.ctx.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
@ -224,7 +212,7 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
select {
case <-c.ctx.Done():
return nil, c.ctx.Err()
case <-c.running:
default:
}
if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
@ -251,11 +239,6 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
func (c *CheckAttributeReader) Close() error {
c.cancel()
err := c.stdinWriter.Close()
select {
case <-c.running:
default:
close(c.running)
}
return err
}

View file

@ -44,6 +44,6 @@ func RepositoryFromContextOrOpen(ctx context.Context, path string) (*Repository,
return gitRepo, nopCloser(nil), nil
}
gitRepo, err := OpenRepositoryCtx(ctx, path)
gitRepo, err := OpenRepository(ctx, path)
return gitRepo, gitRepo, err
}

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git
@ -35,13 +34,13 @@ type Repository struct {
Ctx context.Context
}
// OpenRepository opens the repository at the given path.
func OpenRepository(repoPath string) (*Repository, error) {
return OpenRepositoryCtx(DefaultContext, repoPath)
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
func openRepositoryWithDefaultContext(repoPath string) (*Repository, error) {
return OpenRepository(DefaultContext, repoPath)
}
// OpenRepositoryCtx opens the repository at the given path within the context.Context
func OpenRepositoryCtx(ctx context.Context, repoPath string) (*Repository, error) {
// OpenRepository opens the repository at the given path within the context.Context
func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git
@ -36,13 +35,13 @@ type Repository struct {
Ctx context.Context
}
// OpenRepository opens the repository at the given path.
func OpenRepository(repoPath string) (*Repository, error) {
return OpenRepositoryCtx(DefaultContext, repoPath)
// openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
func openRepositoryWithDefaultContext(repoPath string) (*Repository, error) {
return OpenRepository(DefaultContext, repoPath)
}
// OpenRepositoryCtx opens the repository at the given path with the provided context.
func OpenRepositoryCtx(ctx context.Context, repoPath string) (*Repository, error) {
// OpenRepository opens the repository at the given path with the provided context.
func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err

View file

@ -8,12 +8,13 @@ import "fmt"
// FileBlame return the Blame object of file
func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) {
return NewCommand(repo.Ctx, "blame", "--root", "--", file).RunInDirBytes(path)
stdout, _, err := NewCommand(repo.Ctx, "blame", "--root", "--", file).RunStdBytes(&RunOpts{Dir: path})
return stdout, err
}
// LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
res, err := NewCommand(repo.Ctx, "blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunInDir(path)
res, _, err := NewCommand(repo.Ctx, "blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunStdString(&RunOpts{Dir: path})
if err != nil {
return nil, err
}

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -15,7 +15,7 @@ import (
func TestRepository_GetBlob_Found(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo1_bare")
r, err := OpenRepository(repoPath)
r, err := openRepositoryWithDefaultContext(repoPath)
assert.NoError(t, err)
defer r.Close()
@ -43,7 +43,7 @@ func TestRepository_GetBlob_Found(t *testing.T) {
func TestRepository_GetBlob_NotExist(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo1_bare")
r, err := OpenRepository(repoPath)
r, err := openRepositoryWithDefaultContext(repoPath)
assert.NoError(t, err)
defer r.Close()
@ -57,7 +57,7 @@ func TestRepository_GetBlob_NotExist(t *testing.T) {
func TestRepository_GetBlob_NoId(t *testing.T) {
repoPath := filepath.Join(testReposDir, "repo1_bare")
r, err := OpenRepository(repoPath)
r, err := openRepositoryWithDefaultContext(repoPath)
assert.NoError(t, err)
defer r.Close()

View file

@ -24,7 +24,7 @@ const PullRequestPrefix = "refs/for/"
// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
_, err := NewCommand(ctx, "show-ref", "--verify", "--", name).RunInDir(repoPath)
_, _, err := NewCommand(ctx, "show-ref", "--verify", "--", name).RunStdString(&RunOpts{Dir: repoPath})
return err == nil
}
@ -46,7 +46,7 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
if repo == nil {
return nil, fmt.Errorf("nil repo")
}
stdout, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -65,13 +65,14 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
// SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(name string) error {
_, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD", BranchPrefix+name).RunStdString(&RunOpts{Dir: repo.Path})
return err
}
// GetDefaultBranch gets default branch of repository.
func (repo *Repository) GetDefaultBranch() (string, error) {
return NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path})
return stdout, err
}
// GetBranch returns a branch by it's name
@ -89,7 +90,7 @@ func (repo *Repository) GetBranch(branch string) (*Branch, error) {
// GetBranchesByPath returns a branch by it's path
// if limit = 0 it will not limit
func GetBranchesByPath(ctx context.Context, path string, skip, limit int) ([]*Branch, int, error) {
gitRepo, err := OpenRepositoryCtx(ctx, path)
gitRepo, err := OpenRepository(ctx, path)
if err != nil {
return nil, 0, err
}
@ -133,7 +134,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
}
cmd.AddArguments("--", name)
_, err := cmd.RunInDir(repo.Path)
_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
return err
}
@ -143,7 +144,7 @@ func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
cmd := NewCommand(repo.Ctx, "branch")
cmd.AddArguments("--", branch, oldbranchOrCommit)
_, err := cmd.RunInDir(repo.Path)
_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
return err
}
@ -156,13 +157,13 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
}
cmd.AddArguments(name, url)
_, err := cmd.RunInDir(repo.Path)
_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
return err
}
// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
_, err := NewCommand(repo.Ctx, "remote", "rm", name).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "remote", "rm", name).RunStdString(&RunOpts{Dir: repo.Path})
return err
}
@ -173,6 +174,6 @@ func (branch *Branch) GetCommit() (*Commit, error) {
// RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error {
_, err := NewCommand(repo.Ctx, "branch", "-m", from, to).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "branch", "-m", from, to).RunStdString(&RunOpts{Dir: repo.Path})
return err
}

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git
@ -13,6 +12,7 @@ import (
"strings"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer"
)
// IsObjectExist returns true if given reference exists in the repository.
@ -82,11 +82,12 @@ func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
}
// WalkReferences walks all the references from the repository
func WalkReferences(ctx context.Context, repoPath string, walkfn func(string) error) (int, error) {
// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty.
func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refname string) error) (int, error) {
repo := RepositoryFromContext(ctx, repoPath)
if repo == nil {
var err error
repo, err = OpenRepositoryCtx(ctx, repoPath)
repo, err = OpenRepository(ctx, repoPath)
if err != nil {
return 0, err
}
@ -101,9 +102,61 @@ func WalkReferences(ctx context.Context, repoPath string, walkfn func(string) er
defer iter.Close()
err = iter.ForEach(func(ref *plumbing.Reference) error {
err := walkfn(string(ref.Name()))
err := walkfn(ref.Hash().String(), string(ref.Name()))
i++
return err
})
return i, err
}
// WalkReferences walks all the references from the repository
func (repo *Repository) WalkReferences(arg ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) {
i := 0
var iter storer.ReferenceIter
var err error
switch arg {
case ObjectTag:
iter, err = repo.gogitRepo.Tags()
case ObjectBranch:
iter, err = repo.gogitRepo.Branches()
default:
iter, err = repo.gogitRepo.References()
}
if err != nil {
return i, err
}
defer iter.Close()
err = iter.ForEach(func(ref *plumbing.Reference) error {
if i < skip {
i++
return nil
}
err := walkfn(ref.Hash().String(), string(ref.Name()))
i++
if err != nil {
return err
}
if limit != 0 && i >= skip+limit {
return storer.ErrStop
}
return nil
})
return i, err
}
// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash
func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) {
var revList []string
iter, err := repo.gogitRepo.References()
if err != nil {
return nil, err
}
err = iter.ForEach(func(ref *plumbing.Reference) error {
if ref.Hash().String() == sha && strings.HasPrefix(string(ref.Name()), prefix) {
revList = append(revList, string(ref.Name()))
}
return nil
})
return revList, err
}

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git
@ -68,13 +67,29 @@ func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
}
// WalkReferences walks all the references from the repository
func WalkReferences(ctx context.Context, repoPath string, walkfn func(string) error) (int, error) {
func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refname string) error) (int, error) {
return walkShowRef(ctx, repoPath, "", 0, 0, walkfn)
}
// WalkReferences walks all the references from the repository
// 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) {
var arg string
switch refType {
case ObjectTag:
arg = "--tags"
case ObjectBranch:
arg = "--heads"
default:
arg = ""
}
return walkShowRef(repo.Ctx, repo.Path, arg, skip, limit, walkfn)
}
// callShowRef return refs, if limit = 0 it will not limit
func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) {
countAll, err = walkShowRef(ctx, repoPath, arg, skip, limit, func(branchName string) error {
countAll, err = walkShowRef(ctx, repoPath, arg, skip, limit, func(_, branchName string) error {
branchName = strings.TrimPrefix(branchName, prefix)
branchNames = append(branchNames, branchName)
@ -83,7 +98,7 @@ func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit
return
}
func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, walkfn func(string) error) (countAll int, err error) {
func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
@ -96,11 +111,10 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal
if arg != "" {
args = append(args, arg)
}
err := NewCommand(ctx, args...).RunWithContext(&RunContext{
Timeout: -1,
Dir: repoPath,
Stdout: stdoutWriter,
Stderr: stderrBuilder,
err := NewCommand(ctx, args...).Run(&RunOpts{
Dir: repoPath,
Stdout: stdoutWriter,
Stderr: stderrBuilder,
})
if err != nil {
if stderrBuilder.Len() == 0 {
@ -130,11 +144,7 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal
for limit == 0 || i < skip+limit {
// The output of show-ref is simply a list:
// <sha> SP <ref> LF
_, err := bufReader.ReadSlice(' ')
for err == bufio.ErrBufferFull {
// This shouldn't happen but we'll tolerate it for the sake of peace
_, err = bufReader.ReadSlice(' ')
}
sha, err := bufReader.ReadString(' ')
if err == io.EOF {
return i, nil
}
@ -154,7 +164,12 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal
if len(branchName) > 0 {
branchName = branchName[:len(branchName)-1]
}
err = walkfn(branchName)
if len(sha) > 0 {
sha = sha[:len(sha)-1]
}
err = walkfn(sha, branchName)
if err != nil {
return i, err
}
@ -175,3 +190,15 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal
}
return i, nil
}
// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash
func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) {
var revList []string
_, err := walkShowRef(repo.Ctx, repo.Path, "", 0, 0, func(walkSha, refname string) error {
if walkSha == sha && strings.HasPrefix(refname, prefix) {
revList = append(revList, refname)
}
return nil
})
return revList, err
}

View file

@ -13,7 +13,7 @@ import (
func TestRepository_GetBranches(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -41,7 +41,7 @@ func TestRepository_GetBranches(t *testing.T) {
func BenchmarkRepository_GetBranches(b *testing.B) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
if err != nil {
b.Fatal(err)
}
@ -54,3 +54,44 @@ func BenchmarkRepository_GetBranches(b *testing.B) {
}
}
}
func TestGetRefsBySha(t *testing.T) {
bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls")
bareRepo5, err := OpenRepository(DefaultContext, bareRepo5Path)
if err != nil {
t.Fatal(err)
}
defer bareRepo5.Close()
// do not exist
branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "")
assert.NoError(t, err)
assert.Len(t, branches, 0)
// refs/pull/1/head
branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix)
assert.NoError(t, err)
assert.EqualValues(t, []string{"refs/pull/1/head"}, branches)
branches, err = bareRepo5.GetRefsBySha("d8e0bbb45f200e67d9a784ce55bd90821af45ebd", BranchPrefix)
assert.NoError(t, err)
assert.EqualValues(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches)
branches, err = bareRepo5.GetRefsBySha("58a4bcc53ac13e7ff76127e0fb518b5262bf09af", BranchPrefix)
assert.NoError(t, err)
assert.EqualValues(t, []string{"refs/heads/test-patch-1"}, branches)
}
func BenchmarkGetRefsBySha(b *testing.B) {
bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls")
bareRepo5, err := OpenRepository(DefaultContext, bareRepo5Path)
if err != nil {
b.Fatal(err)
}
defer bareRepo5.Close()
_, _ = bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "")
_, _ = bareRepo5.GetRefsBySha("d8e0bbb45f200e67d9a784ce55bd90821af45ebd", "")
_, _ = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", "")
_, _ = bareRepo5.GetRefsBySha("58a4bcc53ac13e7ff76127e0fb518b5262bf09af", "")
}

View file

@ -58,12 +58,12 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
relpath = `\` + relpath
}
stdout, err := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, id.String(), "--", relpath).RunInDir(repo.Path)
if err != nil {
return nil, err
stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, id.String(), "--", relpath).RunStdString(&RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
id, err = NewIDFromString(stdout)
id, err := NewIDFromString(stdout)
if err != nil {
return nil, err
}
@ -73,9 +73,9 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
// GetCommitByPath returns the last commit of relative path.
func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
stdout, err := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, "--", relpath).RunInDirBytes(repo.Path)
if err != nil {
return nil, err
stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, "--", relpath).RunStdBytes(&RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
commits, err := repo.parsePrettyFormatLogToList(stdout)
@ -86,8 +86,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
}
func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) {
stdout, err := NewCommand(repo.Ctx, "log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize),
"--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunInDirBytes(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize),
"--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -139,7 +139,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
// search for commits matching given constraints and keywords in commit msg
cmd.AddArguments(args...)
stdout, err := cmd.RunInDirBytes(repo.Path)
stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -161,7 +161,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
hashCmd.AddArguments(v)
// search with given constraints for commit matching sha hash of v
hashMatching, err := hashCmd.RunInDirBytes(repo.Path)
hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil || bytes.Contains(stdout, hashMatching) {
continue
}
@ -175,7 +175,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
}
func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) {
stdout, err := NewCommand(repo.Ctx, "diff", "--name-only", id1, id2).RunInDirBytes(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", id1, id2).RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -185,7 +185,7 @@ func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) {
// FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
// You must ensure that id1 and id2 are valid commit ids.
func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) {
stdout, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z", id1, id2, "--", filename).RunInDirBytes(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z", id1, id2, "--", filename).RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return false, err
}
@ -211,11 +211,10 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
err := NewCommand(repo.Ctx, "log", revision, "--follow",
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page),
prettyLogFormat, "--", file).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
Run(&RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
@ -244,8 +243,8 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
// CommitsByFileAndRangeNoFollow return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) ([]*Commit, error) {
stdout, err := NewCommand(repo.Ctx, "log", revision, "--skip="+strconv.Itoa((page-1)*50),
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "log", revision, "--skip="+strconv.Itoa((page-1)*50),
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -254,11 +253,11 @@ func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, pag
// FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, err := NewCommand(repo.Ctx, "diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", startCommitID+"..."+endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// 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...
stdout, err = NewCommand(repo.Ctx, "diff", "--name-only", startCommitID, endCommitID).RunInDir(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "diff", "--name-only", startCommitID, endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
}
if err != nil {
return 0, err
@ -272,13 +271,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error)
var stdout []byte
var err error
if before == nil {
stdout, err = NewCommand(repo.Ctx, "rev-list", last.ID.String()).RunInDirBytes(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "rev-list", last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
} else {
stdout, err = NewCommand(repo.Ctx, "rev-list", before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "rev-list", before.ID.String()+".."+last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
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.
// previously it would return the results of git rev-list before last so let's try that...
stdout, err = NewCommand(repo.Ctx, "rev-list", before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "rev-list", before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
}
}
if err != nil {
@ -292,13 +291,13 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
var stdout []byte
var err error
if before == nil {
stdout, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunInDirBytes(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
} else {
stdout, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
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.
// previously it would return the results of git rev-list --max-count n before last so let's try that...
stdout, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path)
stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
}
}
if err != nil {
@ -344,9 +343,9 @@ func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
cmd.AddArguments(prettyLogFormat, id.String())
}
stdout, err := cmd.RunInDirBytes(repo.Path)
if err != nil {
return nil, err
stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
formattedLog, err := repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
@ -381,7 +380,7 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, erro
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
if CheckGitVersionAtLeast("2.7.0") == nil {
stdout, err := NewCommand(repo.Ctx, "for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -390,7 +389,7 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error)
return branches, nil
}
stdout, err := NewCommand(repo.Ctx, "branch", "--contains", commit.ID.String()).RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -429,7 +428,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
// IsCommitInBranch check if the commit is on the branch
func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
stdout, err := NewCommand(repo.Ctx, "branch", "--contains", commitID, branch).RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains", commitID, branch).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return false, err
}

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git
@ -50,7 +49,7 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
}
}
actualCommitID, err := NewCommand(repo.Ctx, "rev-parse", "--verify", commitID).RunInDir(repo.Path)
actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", commitID).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") ||
strings.Contains(err.Error(), "fatal: Needed a single revision") {

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git
@ -18,7 +17,7 @@ import (
// ResolveReference resolves a name to a reference
func (repo *Repository) ResolveReference(name string) (string, error) {
stdout, err := NewCommand(repo.Ctx, "show-ref", "--hash", name).RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash", name).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
if strings.Contains(err.Error(), "not a valid ref") {
return "", ErrNotExist{name, ""}
@ -51,19 +50,19 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
// SetReference sets the commit ID string of given reference (e.g. branch or tag).
func (repo *Repository) SetReference(name, commitID string) error {
_, err := NewCommand(repo.Ctx, "update-ref", name, commitID).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "update-ref", name, commitID).RunStdString(&RunOpts{Dir: repo.Path})
return err
}
// RemoveReference removes the given reference (e.g. branch or tag).
func (repo *Repository) RemoveReference(name string) error {
_, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d", name).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d", name).RunStdString(&RunOpts{Dir: repo.Path})
return err
}
// IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool {
_, err := NewCommand(repo.Ctx, "cat-file", "-e", name).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "cat-file", "-e", name).RunStdString(&RunOpts{Dir: repo.Path})
return err == nil
}

View file

@ -13,7 +13,7 @@ import (
func TestRepository_GetCommitBranches(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -40,7 +40,7 @@ func TestRepository_GetCommitBranches(t *testing.T) {
func TestGetTagCommitWithSignature(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -54,7 +54,7 @@ func TestGetTagCommitWithSignature(t *testing.T) {
func TestGetCommitWithBadCommitID(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -66,7 +66,7 @@ func TestGetCommitWithBadCommitID(t *testing.T) {
func TestIsCommitInBranch(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()
@ -81,7 +81,7 @@ func TestIsCommitInBranch(t *testing.T) {
func TestRepository_CommitsBetweenIDs(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo4_commitsbetween")
bareRepo1, err := OpenRepository(bareRepo1Path)
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
assert.NoError(t, err)
defer bareRepo1.Close()

View file

@ -0,0 +1,21 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"context"
"fmt"
)
// WriteCommitGraph write commit graph to speed up repo access
// this requires git v2.18 to be installed
func WriteCommitGraph(ctx context.Context, repoPath string) error {
if CheckGitVersionAtLeast("2.18") == nil {
if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil {
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
}
}
return nil
}

View file

@ -4,7 +4,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -40,13 +40,13 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
if tmpRemote != "origin" {
tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags
_, err := NewCommand(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path})
if err == nil {
base = tmpBaseName
}
}
stdout, err := NewCommand(repo.Ctx, "merge-base", "--", base, head).RunInDir(repo.Path)
stdout, _, err := NewCommand(repo.Ctx, "merge-base", "--", base, head).RunStdString(&RunOpts{Dir: repo.Path})
return strings.TrimSpace(stdout), base, err
}
@ -93,7 +93,8 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string,
// We have a common base - therefore we know that ... should work
if !fileOnly {
logs, err := NewCommand(repo.Ctx, "log", baseCommitID+separator+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
var logs []byte
logs, _, err = NewCommand(repo.Ctx, "log", baseCommitID+separator+headBranch, prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -147,22 +148,20 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
}
if err := NewCommand(repo.Ctx, "diff", "-z", "--name-only", base+separator+head).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
}); err != nil {
if strings.Contains(stderr.String(), "no merge base") {
// git >= 2.28 now returns an error if base and head have become unrelated.
// previously it would return the results of git diff -z --name-only base head so let's try that...
w = &lineCountWriter{}
stderr.Reset()
if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only", base, head).RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only", base, head).Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
}); err == nil {
return w.numLines, nil
}
@ -192,7 +191,7 @@ func GetDiffShortStat(ctx context.Context, repoPath string, args ...string) (num
"--shortstat",
}, args...)
stdout, err := NewCommand(ctx, args...).RunInDir(repoPath)
stdout, _, err := NewCommand(ctx, args...).RunStdString(&RunOpts{Dir: repoPath})
if err != nil {
return 0, 0, 0, err
}
@ -248,26 +247,23 @@ func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, bi
// GetDiff generates and returns patch data between given revisions, optimized for human readability
func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
return NewCommand(repo.Ctx, "diff", "-p", base, head).RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
return NewCommand(repo.Ctx, "diff", "-p", base, head).Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
if CheckGitVersionAtLeast("1.7.7") == nil {
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--patience", base, head).RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--patience", base, head).Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
@ -275,32 +271,38 @@ func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
stderr := new(bytes.Buffer)
err := NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout", base+"..."+head).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout", base, head).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
return err
}
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", base+".."+head).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
return strings.Split(stdout, "\n"), err
}
// GetDiffFromMergeBase generates and return patch data from merge base to head
func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
stderr := new(bytes.Buffer)
err := NewCommand(repo.Ctx, "diff", "-p", "--binary", base+"..."+head).
RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return repo.GetDiffBinary(base, head, w)

View file

@ -24,7 +24,7 @@ func TestGetFormatPatch(t *testing.T) {
}
defer util.RemoveAll(clonedPath)
repo, err := OpenRepository(clonedPath)
repo, err := openRepositoryWithDefaultContext(clonedPath)
if err != nil {
assert.NoError(t, err)
return
@ -52,7 +52,7 @@ func TestGetFormatPatch(t *testing.T) {
func TestReadPatch(t *testing.T) {
// Ensure we can read the patch files
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(bareRepo1Path)
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
if err != nil {
assert.NoError(t, err)
return
@ -91,7 +91,7 @@ func TestReadWritePullHead(t *testing.T) {
}
defer util.RemoveAll(clonedPath)
repo, err := OpenRepository(clonedPath)
repo, err := openRepositoryWithDefaultContext(clonedPath)
if err != nil {
assert.NoError(t, err)
return

View file

@ -34,7 +34,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
Sign: true,
}
value, _ := NewCommand(repo.Ctx, "config", "--get", "commit.gpgsign").RunInDir(repo.Path)
value, _, _ := NewCommand(repo.Ctx, "config", "--get", "commit.gpgsign").RunStdString(&RunOpts{Dir: repo.Path})
sign, valid := ParseBool(strings.TrimSpace(value))
if !sign || !valid {
gpgSettings.Sign = false
@ -42,13 +42,13 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
return gpgSettings, nil
}
signingKey, _ := NewCommand(repo.Ctx, "config", "--get", "user.signingkey").RunInDir(repo.Path)
signingKey, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.signingkey").RunStdString(&RunOpts{Dir: repo.Path})
gpgSettings.KeyID = strings.TrimSpace(signingKey)
defaultEmail, _ := NewCommand(repo.Ctx, "config", "--get", "user.email").RunInDir(repo.Path)
defaultEmail, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.email").RunStdString(&RunOpts{Dir: repo.Path})
gpgSettings.Email = strings.TrimSpace(defaultEmail)
defaultName, _ := NewCommand(repo.Ctx, "config", "--get", "user.name").RunInDir(repo.Path)
defaultName, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.name").RunStdString(&RunOpts{Dir: repo.Path})
gpgSettings.Name = strings.TrimSpace(defaultName)
if err := gpgSettings.LoadPublicKeyContent(); err != nil {

View file

@ -18,7 +18,7 @@ import (
// ReadTreeToIndex reads a treeish to the index
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
if len(treeish) != 40 {
res, err := NewCommand(repo.Ctx, "rev-parse", "--verify", treeish).RunInDir(repo.Path)
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", treeish).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return err
}
@ -38,7 +38,7 @@ func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error
if len(indexFilename) > 0 {
env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0])
}
_, err := NewCommand(repo.Ctx, "read-tree", id.String()).RunInDirWithEnv(repo.Path, env)
_, _, err := NewCommand(repo.Ctx, "read-tree", id.String()).RunStdString(&RunOpts{Dir: repo.Path, Env: env})
if err != nil {
return err
}
@ -69,7 +69,7 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpD
// EmptyIndex empties the index
func (repo *Repository) EmptyIndex() error {
_, err := NewCommand(repo.Ctx, "read-tree", "--empty").RunInDir(repo.Path)
_, _, err := NewCommand(repo.Ctx, "read-tree", "--empty").RunStdString(&RunOpts{Dir: repo.Path})
return err
}
@ -81,7 +81,7 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
cmd.AddArguments(arg)
}
}
res, err := cmd.RunInDirBytes(repo.Path)
res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -106,29 +106,28 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
buffer.WriteByte('\000')
}
}
return cmd.RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdin: bytes.NewReader(buffer.Bytes()),
Stdout: stdout,
Stderr: stderr,
return cmd.Run(&RunOpts{
Dir: repo.Path,
Stdin: bytes.NewReader(buffer.Bytes()),
Stdout: stdout,
Stderr: stderr,
})
}
// AddObjectToIndex adds the provided object hash to the index at the provided filename
func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error {
cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, object.String(), filename)
_, err := cmd.RunInDir(repo.Path)
_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
return err
}
// WriteTree writes the current index as a tree to the object db and returns its hash
func (repo *Repository) WriteTree() (*Tree, error) {
res, err := NewCommand(repo.Ctx, "write-tree").RunInDir(repo.Path)
if err != nil {
return nil, err
stdout, _, runErr := NewCommand(repo.Ctx, "write-tree").RunStdString(&RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
id, err := NewIDFromString(strings.TrimSpace(res))
id, err := NewIDFromString(strings.TrimSpace(stdout))
if err != nil {
return nil, err
}

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gogit
// +build gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git

View file

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !gogit
// +build !gogit
package git
@ -16,7 +15,7 @@ import (
func TestRepository_GetLanguageStats(t *testing.T) {
repoPath := filepath.Join(testReposDir, "language_stats_repo")
gitRepo, err := OpenRepository(repoPath)
gitRepo, err := openRepositoryWithDefaultContext(repoPath)
if !assert.NoError(t, err) {
t.Fatal()
}

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