forked from kevadesu/forgejo
Merge branch 'master' into feature-activitypub
This commit is contained in:
commit
07150b33ba
1441 changed files with 45132 additions and 28797 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !pam
|
||||
// +build !pam
|
||||
|
||||
package pam
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build test_avatar_identicon
|
||||
// +build test_avatar_identicon
|
||||
|
||||
package identicon
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
37
modules/cache/cache.go
vendored
37
modules/cache/cache.go
vendored
|
@ -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
|
||||
|
|
33
modules/cache/cache_redis.go
vendored
33
modules/cache/cache_redis.go
vendored
|
@ -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
14
modules/container/map.go
Normal 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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
110
modules/context/package.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 ["
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
56
modules/context/utils.go
Normal 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
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -12,5 +12,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, filepath.Join("..", ".."))
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
GiteaRootPath: filepath.Join("..", ".."),
|
||||
})
|
||||
}
|
||||
|
|
53
modules/convert/package.go
Normal file
53
modules/convert/package.go
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
39
modules/git/command_race_test.go
Normal file
39
modules/git/command_race_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, ""}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
84
modules/git/foreachref/format.go
Normal file
84
modules/git/foreachref/format.go
Normal 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
|
||||
}
|
67
modules/git/foreachref/format_test.go
Normal file
67
modules/git/foreachref/format_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
131
modules/git/foreachref/parser.go
Normal file
131
modules/git/foreachref/parser.go
Normal 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
|
||||
}
|
228
modules/git/foreachref/parser_test.go
Normal file
228
modules/git/foreachref/parser_test.go
Normal 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)
|
||||
}
|
|
@ -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})
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package pipeline
|
||||
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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", "")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
21
modules/git/repo_commitgraph.go
Normal file
21
modules/git/repo_commitgraph.go
Normal 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
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build gogit
|
||||
// +build gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !gogit
|
||||
// +build !gogit
|
||||
|
||||
package git
|
||||
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue