aboutsummaryrefslogtreecommitdiffstats
path: root/services/context
diff options
context:
space:
mode:
Diffstat (limited to 'services/context')
-rw-r--r--services/context/access_log.go3
-rw-r--r--services/context/access_log_test.go2
-rw-r--r--services/context/api.go68
-rw-r--r--services/context/api_test.go2
-rw-r--r--services/context/base.go4
-rw-r--r--services/context/base_form.go4
-rw-r--r--services/context/base_test.go4
-rw-r--r--services/context/context.go20
-rw-r--r--services/context/context_response.go4
-rw-r--r--services/context/org.go6
-rw-r--r--services/context/package.go2
-rw-r--r--services/context/pagination.go14
-rw-r--r--services/context/permission.go7
-rw-r--r--services/context/private.go12
-rw-r--r--services/context/repo.go172
-rw-r--r--services/context/upload/upload.go28
-rw-r--r--services/context/user.go2
17 files changed, 209 insertions, 145 deletions
diff --git a/services/context/access_log.go b/services/context/access_log.go
index 925e4a3056..caade113a7 100644
--- a/services/context/access_log.go
+++ b/services/context/access_log.go
@@ -5,7 +5,6 @@ package context
import (
"bytes"
- "fmt"
"net"
"net/http"
"strings"
@@ -47,7 +46,7 @@ func parseRequestIDFromRequestHeader(req *http.Request) string {
}
}
if len(requestID) > maxRequestIDByteLength {
- requestID = fmt.Sprintf("%s...", requestID[:maxRequestIDByteLength])
+ requestID = requestID[:maxRequestIDByteLength] + "..."
}
return requestID
}
diff --git a/services/context/access_log_test.go b/services/context/access_log_test.go
index c40ef9acd1..139a6eb217 100644
--- a/services/context/access_log_test.go
+++ b/services/context/access_log_test.go
@@ -59,7 +59,7 @@ func TestAccessLogger(t *testing.T) {
recorder.logger = mockLogger
req := &http.Request{
RemoteAddr: "remote-addr",
- Method: "GET",
+ Method: http.MethodGet,
Proto: "https",
URL: &url.URL{Path: "/path"},
}
diff --git a/services/context/api.go b/services/context/api.go
index 10fad419ba..ab50a360f4 100644
--- a/services/context/api.go
+++ b/services/context/api.go
@@ -9,11 +9,14 @@ import (
"fmt"
"net/http"
"net/url"
+ "slices"
+ "strconv"
"strings"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
@@ -168,7 +171,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
if paginater.HasNext() {
u := *curURL
queries := u.Query()
- queries.Set("page", fmt.Sprintf("%d", paginater.Next()))
+ queries.Set("page", strconv.Itoa(paginater.Next()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"next\"", setting.AppURL, u.RequestURI()[1:]))
@@ -176,7 +179,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
if !paginater.IsLast() {
u := *curURL
queries := u.Query()
- queries.Set("page", fmt.Sprintf("%d", paginater.TotalPages()))
+ queries.Set("page", strconv.Itoa(paginater.TotalPages()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"last\"", setting.AppURL, u.RequestURI()[1:]))
@@ -192,7 +195,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
if paginater.HasPrevious() {
u := *curURL
queries := u.Query()
- queries.Set("page", fmt.Sprintf("%d", paginater.Previous()))
+ queries.Set("page", strconv.Itoa(paginater.Previous()))
u.RawQuery = queries.Encode()
links = append(links, fmt.Sprintf("<%s%s>; rel=\"prev\"", setting.AppURL, u.RequestURI()[1:]))
@@ -225,7 +228,7 @@ func APIContexter() func(http.Handler) http.Handler {
ctx.SetContextValue(apiContextKey, ctx)
// 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") {
+ if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.APIErrorInternal(err)
return
@@ -243,8 +246,8 @@ func APIContexter() func(http.Handler) http.Handler {
// APIErrorNotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func (ctx *APIContext) APIErrorNotFound(objs ...any) {
- message := ctx.Locale.TrString("error.not_found")
- var errors []string
+ var message string
+ var errs []string
for _, obj := range objs {
// Ignore nil
if obj == nil {
@@ -252,16 +255,15 @@ func (ctx *APIContext) APIErrorNotFound(objs ...any) {
}
if err, ok := obj.(error); ok {
- errors = append(errors, err.Error())
+ errs = append(errs, err.Error())
} else {
message = obj.(string)
}
}
-
ctx.JSON(http.StatusNotFound, map[string]any{
- "message": message,
+ "message": util.IfZero(message, "not found"), // do not use locale in API
"url": setting.API.SwaggerURL,
- "errors": errors,
+ "errors": errs,
})
}
@@ -297,39 +299,27 @@ func RepoRefForAPI(next http.Handler) http.Handler {
}
if ctx.Repo.GitRepo == nil {
- ctx.APIErrorInternal(fmt.Errorf("no open git repo"))
- return
+ panic("no GitRepo, forgot to call the middleware?") // it is a programming error
}
- refName, _, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.PathParam("*"), ctx.FormTrim("ref"))
+ refName, refType, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.PathParam("*"), ctx.FormTrim("ref"))
var err error
-
- if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refName) {
+ switch refType {
+ case git.RefTypeBranch:
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if gitrepo.IsTagExist(ctx, ctx.Repo.Repository, refName) {
+ case git.RefTypeTag:
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) == ctx.Repo.GetObjectFormat().FullLength() {
- ctx.Repo.CommitID = refName
+ case git.RefTypeCommit:
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
- if err != nil {
- ctx.APIErrorNotFound("GetCommit", err)
- return
- }
- } else {
- ctx.APIErrorNotFound(fmt.Errorf("not exist: '%s'", ctx.PathParam("*")))
+ }
+ if ctx.Repo.Commit == nil || errors.Is(err, util.ErrNotExist) {
+ ctx.APIErrorNotFound("unable to find a git ref")
+ return
+ } else if err != nil {
+ ctx.APIErrorInternal(err)
return
}
-
+ ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
next.ServeHTTP(w, req)
})
}
@@ -375,11 +365,5 @@ func (ctx *APIContext) IsUserRepoAdmin() bool {
// IsUserRepoWriter returns true if current user has "write" privilege in current repo
func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool {
- for _, unitType := range unitTypes {
- if ctx.Repo.CanWrite(unitType) {
- return true
- }
- }
-
- return false
+ return slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite)
}
diff --git a/services/context/api_test.go b/services/context/api_test.go
index 911a49949e..87d74004db 100644
--- a/services/context/api_test.go
+++ b/services/context/api_test.go
@@ -45,6 +45,6 @@ func TestGenAPILinks(t *testing.T) {
links := genAPILinks(u, 100, 20, curPage)
- assert.EqualValues(t, links, response)
+ assert.Equal(t, links, response)
}
}
diff --git a/services/context/base.go b/services/context/base.go
index 3701668bf6..28d6656fd1 100644
--- a/services/context/base.go
+++ b/services/context/base.go
@@ -8,6 +8,7 @@ import (
"html/template"
"io"
"net/http"
+ "strconv"
"strings"
"code.gitea.io/gitea/modules/httplib"
@@ -53,7 +54,7 @@ func (b *Base) AppendAccessControlExposeHeaders(names ...string) {
// SetTotalCountHeader set "X-Total-Count" header
func (b *Base) SetTotalCountHeader(total int64) {
- b.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
+ b.RespHeader().Set("X-Total-Count", strconv.FormatInt(total, 10))
b.AppendAccessControlExposeHeaders("X-Total-Count")
}
@@ -82,6 +83,7 @@ func (b *Base) RespHeader() http.Header {
}
// HTTPError returned an error to web browser
+// FIXME: many calls to this HTTPError are not right: it shouldn't expose err.Error() directly, it doesn't accept more than one content
func (b *Base) HTTPError(status int, contents ...string) {
v := http.StatusText(status)
if len(contents) > 0 {
diff --git a/services/context/base_form.go b/services/context/base_form.go
index 5b8cae9e99..81fd7cd328 100644
--- a/services/context/base_form.go
+++ b/services/context/base_form.go
@@ -12,6 +12,8 @@ import (
)
// FormString returns the first value matching the provided key in the form as a string
+// It works the same as http.Request.FormValue:
+// try urlencoded request body first, then query string, then multipart form body
func (b *Base) FormString(key string, def ...string) string {
s := b.Req.FormValue(key)
if s == "" {
@@ -20,7 +22,7 @@ func (b *Base) FormString(key string, def ...string) string {
return s
}
-// FormStrings returns a string slice for the provided key from the form
+// FormStrings returns a values for the key in the form (including query parameters), similar to FormString
func (b *Base) FormStrings(key string) []string {
if b.Req.Form == nil {
if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
diff --git a/services/context/base_test.go b/services/context/base_test.go
index b936b76f58..2a4f86dddf 100644
--- a/services/context/base_test.go
+++ b/services/context/base_test.go
@@ -15,7 +15,7 @@ import (
func TestRedirect(t *testing.T) {
setting.IsInTesting = true
- req, _ := http.NewRequest("GET", "/", nil)
+ req, _ := http.NewRequest(http.MethodGet, "/", nil)
cases := []struct {
url string
@@ -36,7 +36,7 @@ func TestRedirect(t *testing.T) {
assert.Equal(t, c.keep, has, "url = %q", c.url)
}
- req, _ = http.NewRequest("GET", "/", nil)
+ req, _ = http.NewRequest(http.MethodGet, "/", nil)
resp := httptest.NewRecorder()
req.Header.Add("HX-Request", "true")
b := NewBaseContextForTest(resp, req)
diff --git a/services/context/context.go b/services/context/context.go
index 79bc5da920..32ec260aab 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -23,6 +23,7 @@ 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"
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"
@@ -184,7 +185,7 @@ func Contexter() func(next http.Handler) http.Handler {
})
// 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") {
+ if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.ServerError("ParseMultipartForm", err)
return
@@ -196,6 +197,8 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Data["SystemConfig"] = setting.Config()
+ ctx.Data["ShowTwoFactorRequiredMessage"] = ctx.DoerNeedTwoFactorAuth()
+
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["DisableStars"] = setting.Repository.DisableStars
@@ -209,6 +212,13 @@ func Contexter() func(next http.Handler) http.Handler {
}
}
+func (ctx *Context) DoerNeedTwoFactorAuth() bool {
+ if !setting.TwoFactorAuthEnforced {
+ return false
+ }
+ return ctx.Session.Get(session.KeyUserHasTwoFactorAuth) == false
+}
+
// HasError returns true if error occurs in form validation.
// Attention: this function changes ctx.Data and ctx.Flash
// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
@@ -252,3 +262,11 @@ func (ctx *Context) JSONError(msg any) {
panic(fmt.Sprintf("unsupported type: %T", msg))
}
}
+
+func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
+ msg := util.OptionalArg(optMsg)
+ if msg == "" {
+ msg = ctx.Locale.TrString("error.not_found")
+ }
+ ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"})
+}
diff --git a/services/context/context_response.go b/services/context/context_response.go
index 61b432395a..3f64fc7352 100644
--- a/services/context/context_response.go
+++ b/services/context/context_response.go
@@ -92,7 +92,7 @@ func (ctx *Context) HTML(status int, name templates.TplName) {
}
// JSONTemplate renders the template as JSON response
-// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
+// keep in mind that the template is processed in HTML context, so JSON things should be handled carefully, e.g.: use JSEscape
func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
if err != nil {
@@ -150,7 +150,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
ctx.Data["Title"] = "Page Not Found"
- ctx.HTML(http.StatusNotFound, templates.TplName("status/404"))
+ ctx.HTML(http.StatusNotFound, "status/404")
}
// ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
diff --git a/services/context/org.go b/services/context/org.go
index 992a48afa0..1cd8923178 100644
--- a/services/context/org.go
+++ b/services/context/org.go
@@ -182,7 +182,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) {
return
}
for _, team := range teams {
- if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
+ if team.IncludesAllRepositories && team.HasAdminAccess() {
shouldSeeAllTeams = true
break
}
@@ -208,7 +208,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) {
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {
- if team.LowerName == strings.ToLower(teamName) {
+ if strings.EqualFold(team.LowerName, teamName) {
teamExists = true
ctx.Org.Team = team
ctx.Org.IsTeamMember = true
@@ -228,7 +228,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) {
return
}
- ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin
+ ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.HasAdminAccess()
ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin
if opts.RequireTeamAdmin && !ctx.Org.IsTeamAdmin {
ctx.NotFound(err)
diff --git a/services/context/package.go b/services/context/package.go
index 64bb4f3ecd..8b722932b1 100644
--- a/services/context/package.go
+++ b/services/context/package.go
@@ -93,7 +93,7 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package
}
func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) {
- if setting.Service.RequireSignInView && (doer == nil || doer.IsGhost()) {
+ if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) {
return perm.AccessModeNone, nil
}
diff --git a/services/context/pagination.go b/services/context/pagination.go
index d33dd217d0..2a9805db05 100644
--- a/services/context/pagination.go
+++ b/services/context/pagination.go
@@ -21,14 +21,20 @@ type Pagination struct {
// NewPagination creates a new instance of the Pagination struct.
// "pagingNum" is "page size" or "limit", "current" is "page"
+// total=-1 means only showing prev/next
func NewPagination(total, pagingNum, current, numPages int) *Pagination {
p := &Pagination{}
p.Paginater = paginator.New(total, pagingNum, current, numPages)
return p
}
-func (p *Pagination) AddParamFromRequest(req *http.Request) {
- for key, values := range req.URL.Query() {
+func (p *Pagination) WithCurRows(n int) *Pagination {
+ p.Paginater.SetCurRows(n)
+ return p
+}
+
+func (p *Pagination) AddParamFromQuery(q url.Values) {
+ for key, values := range q {
if key == "page" || len(values) == 0 || (len(values) == 1 && values[0] == "") {
continue
}
@@ -39,6 +45,10 @@ func (p *Pagination) AddParamFromRequest(req *http.Request) {
}
}
+func (p *Pagination) AddParamFromRequest(req *http.Request) {
+ p.AddParamFromQuery(req.URL.Query())
+}
+
// GetParams returns the configured URL params
func (p *Pagination) GetParams() template.URL {
return template.URL(strings.Join(p.urlParams, "&"))
diff --git a/services/context/permission.go b/services/context/permission.go
index 7055f798da..c0a5a98724 100644
--- a/services/context/permission.go
+++ b/services/context/permission.go
@@ -5,6 +5,7 @@ package context
import (
"net/http"
+ "slices"
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
@@ -34,10 +35,8 @@ func CanWriteToBranch() func(ctx *Context) {
// RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission
func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
- for _, unitType := range unitTypes {
- if ctx.Repo.CanWrite(unitType) {
- return
- }
+ if slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) {
+ return
}
ctx.NotFound(nil)
}
diff --git a/services/context/private.go b/services/context/private.go
index 51857da8fe..d20e49f588 100644
--- a/services/context/private.go
+++ b/services/context/private.go
@@ -5,7 +5,6 @@ package context
import (
"context"
- "fmt"
"net/http"
"time"
@@ -29,7 +28,6 @@ func init() {
})
}
-// Deadline is part of the interface for context.Context and we pass this to the request context
func (ctx *PrivateContext) Deadline() (deadline time.Time, ok bool) {
if ctx.Override != nil {
return ctx.Override.Deadline()
@@ -37,7 +35,6 @@ func (ctx *PrivateContext) Deadline() (deadline time.Time, ok bool) {
return ctx.Base.Deadline()
}
-// Done is part of the interface for context.Context and we pass this to the request context
func (ctx *PrivateContext) Done() <-chan struct{} {
if ctx.Override != nil {
return ctx.Override.Done()
@@ -45,7 +42,6 @@ func (ctx *PrivateContext) Done() <-chan struct{} {
return ctx.Base.Done()
}
-// Err is part of the interface for context.Context and we pass this to the request context
func (ctx *PrivateContext) Err() error {
if ctx.Override != nil {
return ctx.Override.Err()
@@ -53,14 +49,14 @@ func (ctx *PrivateContext) Err() error {
return ctx.Base.Err()
}
-var privateContextKey any = "default_private_context"
+type privateContextKeyType struct{}
+
+var privateContextKey privateContextKeyType
-// GetPrivateContext returns a context for Private routes
func GetPrivateContext(req *http.Request) *PrivateContext {
return req.Context().Value(privateContextKey).(*PrivateContext)
}
-// PrivateContexter returns apicontext as middleware
func PrivateContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@@ -82,7 +78,7 @@ func OverrideContext() func(http.Handler) http.Handler {
// 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 := GetPrivateContext(req)
var finished func()
- ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
+ ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "PrivateContext: "+ctx.Req.RequestURI, process.RequestProcessType, true)
defer finished()
next.ServeHTTP(ctx.Resp, ctx.Req)
})
diff --git a/services/context/repo.go b/services/context/repo.go
index 6eccd1312a..afc6de9b16 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -71,11 +71,6 @@ func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.User
return issues_model.CanMaintainerWriteToBranch(ctx, r.Permission, branch, user)
}
-// CanEnableEditor returns true if repository is editable and user has proper access level.
-func (r *Repository) CanEnableEditor(ctx context.Context, user *user_model.User) bool {
- return r.RefFullName.IsBranch() && r.CanWriteToBranch(ctx, user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
-}
-
// CanCreateBranch returns true if repository is editable and user has proper access level.
func (r *Repository) CanCreateBranch() bool {
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
@@ -94,58 +89,100 @@ func RepoMustNotBeArchived() func(ctx *Context) {
}
}
-// CanCommitToBranchResults represents the results of CanCommitToBranch
-type CanCommitToBranchResults struct {
- CanCommitToBranch bool
- EditorEnabled bool
- UserCanPush bool
- RequireSigned bool
- WillSign bool
- SigningKey string
- WontSignReason string
+type CommitFormOptions struct {
+ NeedFork bool
+
+ TargetRepo *repo_model.Repository
+ TargetFormAction string
+ WillSubmitToFork bool
+ CanCommitToBranch bool
+ UserCanPush bool
+ RequireSigned bool
+ WillSign bool
+ SigningKey *git.SigningKey
+ WontSignReason string
+ CanCreatePullRequest bool
+ CanCreateBasePullRequest bool
}
-// CanCommitToBranch returns true if repository is editable and user has proper access level
-//
-// and branch is not protected for push
-func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
- protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName)
+func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *repo_model.Repository, doerRepoPerm access_model.Permission, refName git.RefName) (*CommitFormOptions, error) {
+ if !refName.IsBranch() {
+ // it shouldn't happen because middleware already checks
+ return nil, util.NewInvalidArgumentErrorf("ref %q is not a branch", refName)
+ }
+
+ originRepo := targetRepo
+ branchName := refName.ShortName()
+ // TODO: CanMaintainerWriteToBranch is a bad name, but it really does what "CanWriteToBranch" does
+ if !issues_model.CanMaintainerWriteToBranch(ctx, doerRepoPerm, branchName, doer) {
+ targetRepo = repo_model.GetForkedRepo(ctx, doer.ID, targetRepo.ID)
+ if targetRepo == nil {
+ return &CommitFormOptions{NeedFork: true}, nil
+ }
+ // now, we get our own forked repo; it must be writable by us.
+ }
+ submitToForkedRepo := targetRepo.ID != originRepo.ID
+ err := targetRepo.GetBaseRepo(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, targetRepo.ID, branchName)
if err != nil {
- return CanCommitToBranchResults{}, err
+ return nil, err
}
- userCanPush := true
- requireSigned := false
+ canPushWithProtection := true
+ protectionRequireSigned := false
if protectedBranch != nil {
- protectedBranch.Repo = r.Repository
- userCanPush = protectedBranch.CanUserPush(ctx, doer)
- requireSigned = protectedBranch.RequireSignedCommits
+ protectedBranch.Repo = targetRepo
+ canPushWithProtection = protectedBranch.CanUserPush(ctx, doer)
+ protectionRequireSigned = protectedBranch.RequireSignedCommits
}
- sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
-
- canCommit := r.CanEnableEditor(ctx, doer) && userCanPush
- if requireSigned {
- canCommit = canCommit && sign
- }
+ willSign, signKeyID, _, err := asymkey_service.SignCRUDAction(ctx, targetRepo.RepoPath(), doer, targetRepo.RepoPath(), refName.String())
wontSignReason := ""
- if err != nil {
- if asymkey_service.IsErrWontSign(err) {
- wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
- err = nil
- } else {
- wontSignReason = "error"
- }
+ if asymkey_service.IsErrWontSign(err) {
+ wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
+ } else if err != nil {
+ return nil, err
}
- return CanCommitToBranchResults{
- CanCommitToBranch: canCommit,
- EditorEnabled: r.CanEnableEditor(ctx, doer),
- UserCanPush: userCanPush,
- RequireSigned: requireSigned,
- WillSign: sign,
- SigningKey: keyID,
+ canCommitToBranch := !submitToForkedRepo /* same repo */ && targetRepo.CanEnableEditor() && canPushWithProtection
+ if protectionRequireSigned {
+ canCommitToBranch = canCommitToBranch && willSign
+ }
+
+ canCreateBasePullRequest := targetRepo.BaseRepo != nil && targetRepo.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests)
+ canCreatePullRequest := targetRepo.UnitEnabled(ctx, unit_model.TypePullRequests) || canCreateBasePullRequest
+
+ opts := &CommitFormOptions{
+ TargetRepo: targetRepo,
+ WillSubmitToFork: submitToForkedRepo,
+ CanCommitToBranch: canCommitToBranch,
+ UserCanPush: canPushWithProtection,
+ RequireSigned: protectionRequireSigned,
+ WillSign: willSign,
+ SigningKey: signKeyID,
WontSignReason: wontSignReason,
- }, err
+
+ CanCreatePullRequest: canCreatePullRequest,
+ CanCreateBasePullRequest: canCreateBasePullRequest,
+ }
+ editorAction := ctx.PathParam("editor_action")
+ editorPathParamRemaining := util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+ if submitToForkedRepo {
+ // there is only "default branch" in forked repo, we will use "from_base_branch" to get a new branch from base repo
+ editorPathParamRemaining = util.PathEscapeSegments(targetRepo.DefaultBranch) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + "?from_base_branch=" + url.QueryEscape(branchName)
+ }
+ if editorAction == "_cherrypick" {
+ opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + ctx.PathParam("sha") + "/" + editorPathParamRemaining
+ } else {
+ opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + editorPathParamRemaining
+ }
+ if ctx.Req.URL.RawQuery != "" {
+ opts.TargetFormAction += util.Iif(strings.Contains(opts.TargetFormAction, "?"), "&", "?") + ctx.Req.URL.RawQuery
+ }
+ return opts, nil
}
// CanUseTimetracker returns whether a user can use the timetracker.
@@ -328,7 +365,9 @@ func RedirectToRepo(ctx *Base, redirectRepoID int64) {
if ctx.Req.URL.RawQuery != "" {
redirectPath += "?" + ctx.Req.URL.RawQuery
}
- ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
+ // Git client needs a 301 redirect by default to follow the new location
+ // It's not documentated in git documentation, but it's the behavior of git client
+ ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusMovedPermanently)
}
func repoAssignment(ctx *Context, repo *repo_model.Repository) {
@@ -338,13 +377,17 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
return
}
- ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
+ if ctx.DoerNeedTwoFactorAuth() {
+ ctx.Repo.Permission = access_model.PermissionNoAccess()
+ } else {
+ ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
+ }
}
- if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() && !canWriteAsMaintainer(ctx) {
+ if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
if ctx.FormString("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
@@ -386,7 +429,7 @@ func RepoAssignment(ctx *Context) {
}
// Check if the user is the same as the repository owner
- if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
+ if ctx.IsSigned && strings.EqualFold(ctx.Doer.LowerName, userName) {
ctx.Repo.Owner = ctx.Doer
} else {
ctx.Repo.Owner, err = user_model.GetUserByName(ctx, userName)
@@ -667,12 +710,6 @@ func RepoAssignment(ctx *Context) {
const headRefName = "HEAD"
-func RepoRef() func(*Context) {
- // old code does: return RepoRefByType(git.RefTypeBranch)
- // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong)
- return nil
-}
-
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
refName := ""
parts := strings.Split(path, "/")
@@ -795,8 +832,8 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
return func(ctx *Context) {
var err error
refType := detectRefType
- if ctx.Repo.Repository.IsBeingCreated() {
- return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl"
+ if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
+ return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl", or empty repo guide
}
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
@@ -815,9 +852,9 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
if reqPath == "" {
refShortName = ctx.Repo.Repository.DefaultBranch
if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refShortName) {
- brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
+ brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 1)
if err == nil && len(brs) != 0 {
- refShortName = brs[0].Name
+ refShortName = brs[0]
} else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
} else {
@@ -936,6 +973,15 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
ctx.ServerError("GetCommitsCount", err)
return
}
+ if ctx.Repo.RefFullName.IsTag() {
+ rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Repo.RefFullName.TagName())
+ if err == nil && rel.NumCommits <= 0 {
+ rel.NumCommits = ctx.Repo.CommitsCount
+ if err := repo_model.UpdateReleaseNumCommits(ctx, rel); err != nil {
+ log.Error("UpdateReleaseNumCommits", err)
+ }
+ }
+ }
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
}
diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go
index da4370a433..23707950d4 100644
--- a/services/context/upload/upload.go
+++ b/services/context/upload/upload.go
@@ -11,7 +11,9 @@ import (
"regexp"
"strings"
+ repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
)
@@ -39,7 +41,7 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error {
allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format
allowedTypes := []string{}
- for _, entry := range strings.Split(allowedTypesStr, ",") {
+ for entry := range strings.SplitSeq(allowedTypesStr, ",") {
entry = strings.ToLower(strings.TrimSpace(entry))
if entry != "" {
allowedTypes = append(allowedTypes, entry)
@@ -87,14 +89,15 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error {
// AddUploadContext renders template values for dropzone
func AddUploadContext(ctx *context.Context, uploadType string) {
- if uploadType == "release" {
+ switch uploadType {
+ case "release":
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/releases/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/releases/attachments/remove"
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/releases/attachments"
ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Release.AllowedTypes, "|", ",")
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
- } else if uploadType == "comment" {
+ case "comment":
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
if len(ctx.PathParam("index")) > 0 {
@@ -105,12 +108,17 @@ func AddUploadContext(ctx *context.Context, uploadType string) {
ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Attachment.AllowedTypes, "|", ",")
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
- } else if uploadType == "repo" {
- ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/upload-file"
- ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/upload-remove"
- ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/upload-file"
- ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Upload.AllowedTypes, "|", ",")
- ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
- ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
+ default:
+ setting.PanicInDevOrTesting("Invalid upload type: %s", uploadType)
}
}
+
+func AddUploadContextForRepo(ctx reqctx.RequestContext, repo *repo_model.Repository) {
+ ctxData, repoLink := ctx.GetData(), repo.Link()
+ ctxData["UploadUrl"] = repoLink + "/upload-file"
+ ctxData["UploadRemoveUrl"] = repoLink + "/upload-remove"
+ ctxData["UploadLinkUrl"] = repoLink + "/upload-file"
+ ctxData["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Upload.AllowedTypes, "|", ",")
+ ctxData["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
+ ctxData["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
+}
diff --git a/services/context/user.go b/services/context/user.go
index c09ded8339..f1a3035ee9 100644
--- a/services/context/user.go
+++ b/services/context/user.go
@@ -61,7 +61,7 @@ func UserAssignmentAPI() func(ctx *APIContext) {
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (contextUser *user_model.User) {
username := ctx.PathParam("username")
- if doer != nil && doer.LowerName == strings.ToLower(username) {
+ if doer != nil && strings.EqualFold(doer.LowerName, username) {
contextUser = doer
} else {
var err error