aboutsummaryrefslogtreecommitdiffstats
path: root/services/context
diff options
context:
space:
mode:
Diffstat (limited to 'services/context')
-rw-r--r--services/context/access_log.go103
-rw-r--r--services/context/access_log_test.go71
-rw-r--r--services/context/api.go31
-rw-r--r--services/context/base.go180
-rw-r--r--services/context/base_form.go77
-rw-r--r--services/context/base_path.go47
-rw-r--r--services/context/base_test.go7
-rw-r--r--services/context/context.go26
-rw-r--r--services/context/context_model.go20
-rw-r--r--services/context/context_response.go2
-rw-r--r--services/context/context_test.go4
-rw-r--r--services/context/org.go6
-rw-r--r--services/context/package.go6
-rw-r--r--services/context/pagination.go24
-rw-r--r--services/context/permission.go78
-rw-r--r--services/context/private.go21
-rw-r--r--services/context/repo.go481
-rw-r--r--services/context/response.go36
-rw-r--r--services/context/upload/upload.go4
-rw-r--r--services/context/user.go4
20 files changed, 546 insertions, 682 deletions
diff --git a/services/context/access_log.go b/services/context/access_log.go
index 0926748ac5..001d93a362 100644
--- a/services/context/access_log.go
+++ b/services/context/access_log.go
@@ -18,13 +18,14 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
)
-type routerLoggerOptions struct {
- req *http.Request
+type accessLoggerTmplData struct {
Identity *string
Start *time.Time
- ResponseWriter http.ResponseWriter
- Ctx map[string]any
- RequestID *string
+ ResponseWriter struct {
+ Status, Size int
+ }
+ Ctx map[string]any
+ RequestID *string
}
const keyOfRequestIDInTemplate = ".RequestID"
@@ -51,51 +52,65 @@ func parseRequestIDFromRequestHeader(req *http.Request) string {
return requestID
}
+type accessLogRecorder struct {
+ logger log.BaseLogger
+ logTemplate *template.Template
+ needRequestID bool
+}
+
+func (lr *accessLogRecorder) record(start time.Time, respWriter ResponseWriter, req *http.Request) {
+ var requestID string
+ if lr.needRequestID {
+ requestID = parseRequestIDFromRequestHeader(req)
+ }
+
+ reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
+ if err != nil {
+ reqHost = req.RemoteAddr
+ }
+
+ identity := "-"
+ data := middleware.GetContextData(req.Context())
+ if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
+ identity = signedUser.Name
+ }
+ buf := bytes.NewBuffer([]byte{})
+ tmplData := accessLoggerTmplData{
+ Identity: &identity,
+ Start: &start,
+ Ctx: map[string]any{
+ "RemoteAddr": req.RemoteAddr,
+ "RemoteHost": reqHost,
+ "Req": req,
+ },
+ RequestID: &requestID,
+ }
+ tmplData.ResponseWriter.Status = respWriter.WrittenStatus()
+ tmplData.ResponseWriter.Size = respWriter.WrittenSize()
+ err = lr.logTemplate.Execute(buf, tmplData)
+ if err != nil {
+ log.Error("Could not execute access logger template: %v", err.Error())
+ }
+
+ lr.logger.Log(1, log.INFO, "%s", buf.String())
+}
+
+func newAccessLogRecorder() *accessLogRecorder {
+ return &accessLogRecorder{
+ logger: log.GetLogger("access"),
+ logTemplate: template.Must(template.New("log").Parse(setting.Log.AccessLogTemplate)),
+ needRequestID: len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate),
+ }
+}
+
// AccessLogger returns a middleware to log access logger
func AccessLogger() func(http.Handler) http.Handler {
- logger := log.GetLogger("access")
- needRequestID := len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate)
- logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate)
+ recorder := newAccessLogRecorder()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
start := time.Now()
-
- var requestID string
- if needRequestID {
- requestID = parseRequestIDFromRequestHeader(req)
- }
-
- reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
- if err != nil {
- reqHost = req.RemoteAddr
- }
-
next.ServeHTTP(w, req)
- rw := w.(ResponseWriter)
-
- identity := "-"
- data := middleware.GetContextData(req.Context())
- if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
- identity = signedUser.Name
- }
- buf := bytes.NewBuffer([]byte{})
- err = logTemplate.Execute(buf, routerLoggerOptions{
- req: req,
- Identity: &identity,
- Start: &start,
- ResponseWriter: rw,
- Ctx: map[string]any{
- "RemoteAddr": req.RemoteAddr,
- "RemoteHost": reqHost,
- "Req": req,
- },
- RequestID: &requestID,
- })
- if err != nil {
- log.Error("Could not execute access logger template: %v", err.Error())
- }
-
- logger.Info("%s", buf.String())
+ recorder.record(start, w.(ResponseWriter), req)
})
}
}
diff --git a/services/context/access_log_test.go b/services/context/access_log_test.go
new file mode 100644
index 0000000000..bd3e47e0cc
--- /dev/null
+++ b/services/context/access_log_test.go
@@ -0,0 +1,71 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type testAccessLoggerMock struct {
+ logs []string
+}
+
+func (t *testAccessLoggerMock) Log(skip int, level log.Level, format string, v ...any) {
+ t.logs = append(t.logs, fmt.Sprintf(format, v...))
+}
+
+func (t *testAccessLoggerMock) GetLevel() log.Level {
+ return log.INFO
+}
+
+type testAccessLoggerResponseWriterMock struct{}
+
+func (t testAccessLoggerResponseWriterMock) Header() http.Header {
+ return nil
+}
+
+func (t testAccessLoggerResponseWriterMock) Before(f func(ResponseWriter)) {}
+
+func (t testAccessLoggerResponseWriterMock) WriteHeader(statusCode int) {}
+
+func (t testAccessLoggerResponseWriterMock) Write(bytes []byte) (int, error) {
+ return 0, nil
+}
+
+func (t testAccessLoggerResponseWriterMock) Flush() {}
+
+func (t testAccessLoggerResponseWriterMock) WrittenStatus() int {
+ return http.StatusOK
+}
+
+func (t testAccessLoggerResponseWriterMock) WrittenSize() int {
+ return 123123
+}
+
+func TestAccessLogger(t *testing.T) {
+ setting.Log.AccessLogTemplate = `{{.Ctx.RemoteHost}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}" "{{.Ctx.Req.UserAgent}}"`
+ recorder := newAccessLogRecorder()
+ mockLogger := &testAccessLoggerMock{}
+ recorder.logger = mockLogger
+ req := &http.Request{
+ RemoteAddr: "remote-addr",
+ Method: "GET",
+ Proto: "https",
+ URL: &url.URL{Path: "/path"},
+ }
+ req.Header = http.Header{}
+ req.Header.Add("Referer", "referer")
+ req.Header.Add("User-Agent", "user-agent")
+ recorder.record(time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), &testAccessLoggerResponseWriterMock{}, req)
+ assert.Equal(t, []string{`remote-addr - - [02/Jan/2000:03:04:05 +0000] "GET /path https" 200 123123 "referer" "user-agent"`}, mockLogger.logs)
+}
diff --git a/services/context/api.go b/services/context/api.go
index b45e80a329..bdeff0af63 100644
--- a/services/context/api.go
+++ b/services/context/api.go
@@ -5,7 +5,6 @@
package context
import (
- "context"
"fmt"
"net/http"
"net/url"
@@ -212,17 +211,15 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
func APIContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(w, req)
+ base := NewBaseContext(w, req)
ctx := &APIContext{
Base: base,
Cache: cache.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &APIOrganization{},
}
- defer baseCleanUp()
- ctx.Base.AppendContextValue(apiContextKey, ctx)
- ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
+ 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") {
@@ -267,31 +264,22 @@ func (ctx *APIContext) NotFound(objs ...any) {
// 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) {
+func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) {
+ return func(ctx *APIContext) {
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty && !(len(allowEmpty) != 0 && allowEmpty[0]) {
- return nil
+ return
}
// For API calls.
if ctx.Repo.GitRepo == nil {
- gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ var err error
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
- return cancel
- }
- 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
}
}
-
- return cancel
}
}
@@ -305,8 +293,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return
}
- // NOTICE: the "ref" here for internal usage only (e.g. woodpecker)
- refName, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.FormTrim("ref"))
+ refName, _, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.PathParam("*"), ctx.FormTrim("ref"))
var err error
if ctx.Repo.GitRepo.IsBranchExist(refName) {
diff --git a/services/context/base.go b/services/context/base.go
index d627095584..5db84f42a5 100644
--- a/services/context/base.go
+++ b/services/context/base.go
@@ -4,86 +4,39 @@
package context
import (
- "context"
"fmt"
"html/template"
"io"
"net/http"
- "net/url"
- "strconv"
"strings"
- "time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web/middleware"
-
- "github.com/go-chi/chi/v5"
)
-type contextValuePair struct {
- key any
- valueFn func() any
-}
-
type BaseContextKeyType struct{}
var BaseContextKey BaseContextKeyType
type Base struct {
- originCtx context.Context
- contextValues []contextValuePair
+ reqctx.RequestContext
Resp ResponseWriter
Req *http.Request
// Data is prepared by ContextDataStore middleware, this field only refers to the pre-created/prepared ContextData.
// Although it's mainly used for MVC templates, sometimes it's also used to pass data between middlewares/handler
- Data middleware.ContextData
+ Data reqctx.ContextData
// Locale is mainly for Web context, although the API context also uses it in some cases: message response, form validation
Locale translation.Locale
}
-func (b *Base) Deadline() (deadline time.Time, ok bool) {
- return b.originCtx.Deadline()
-}
-
-func (b *Base) Done() <-chan struct{} {
- return b.originCtx.Done()
-}
-
-func (b *Base) Err() error {
- return b.originCtx.Err()
-}
-
-func (b *Base) Value(key any) any {
- for _, pair := range b.contextValues {
- if pair.key == key {
- return pair.valueFn()
- }
- }
- return b.originCtx.Value(key)
-}
-
-func (b *Base) AppendContextValueFunc(key any, valueFn func() any) any {
- b.contextValues = append(b.contextValues, contextValuePair{key, valueFn})
- return b
-}
-
-func (b *Base) AppendContextValue(key, value any) any {
- b.contextValues = append(b.contextValues, contextValuePair{key, func() any { return value }})
- return b
-}
-
-func (b *Base) GetData() middleware.ContextData {
- return b.Data
-}
-
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (b *Base) AppendAccessControlExposeHeaders(names ...string) {
val := b.RespHeader().Get("Access-Control-Expose-Headers")
@@ -147,93 +100,6 @@ func (b *Base) RemoteAddr() string {
return b.Req.RemoteAddr
}
-// PathParam returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
-func (b *Base) PathParam(name string) string {
- s, err := url.PathUnescape(b.PathParamRaw(name))
- if err != nil && !setting.IsProd {
- panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
- }
- return s
-}
-
-// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
-func (b *Base) PathParamRaw(name string) string {
- return chi.URLParam(b.Req, strings.TrimPrefix(name, ":"))
-}
-
-// PathParamInt64 returns the param in request path as int64
-func (b *Base) PathParamInt64(p string) int64 {
- v, _ := strconv.ParseInt(b.PathParam(p), 10, 64)
- return v
-}
-
-// SetPathParam set request path params into routes
-func (b *Base) SetPathParam(k, v string) {
- chiCtx := chi.RouteContext(b)
- chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
-}
-
-// FormString returns the first value matching the provided key in the form as a string
-func (b *Base) FormString(key string) string {
- return b.Req.FormValue(key)
-}
-
-// FormStrings returns a string slice for the provided key from the form
-func (b *Base) FormStrings(key string) []string {
- if b.Req.Form == nil {
- if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
- return nil
- }
- }
- if v, ok := b.Req.Form[key]; ok {
- return v
- }
- return nil
-}
-
-// FormTrim returns the first value for the provided key in the form as a space trimmed string
-func (b *Base) FormTrim(key string) string {
- return strings.TrimSpace(b.Req.FormValue(key))
-}
-
-// FormInt returns the first value for the provided key in the form as an int
-func (b *Base) FormInt(key string) int {
- v, _ := strconv.Atoi(b.Req.FormValue(key))
- return v
-}
-
-// FormInt64 returns the first value for the provided key in the form as an int64
-func (b *Base) FormInt64(key string) int64 {
- v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64)
- return v
-}
-
-// FormBool returns true if the value for the provided key in the form is "1", "true" or "on"
-func (b *Base) FormBool(key string) bool {
- s := b.Req.FormValue(key)
- v, _ := strconv.ParseBool(s)
- v = v || strings.EqualFold(s, "on")
- return v
-}
-
-// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
-// for the provided key exists in the form else it returns optional.None[bool]()
-func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
- value := b.Req.FormValue(key)
- if len(value) == 0 {
- return optional.None[bool]()
- }
- s := b.Req.FormValue(key)
- v, _ := strconv.ParseBool(s)
- v = v || strings.EqualFold(s, "on")
- return optional.Some(v)
-}
-
-func (b *Base) SetFormString(key, value string) {
- _ = b.Req.FormValue(key) // force parse form
- b.Req.Form.Set(key, value)
-}
-
// PlainTextBytes renders bytes as plain text
func (b *Base) plainTextInternal(skip, status int, bs []byte) {
statusPrefix := status / 100
@@ -295,13 +161,6 @@ func (b *Base) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
http.ServeContent(b.Resp, b.Req, opts.Filename, opts.LastModified, r)
}
-// Close frees all resources hold by Context
-func (b *Base) cleanUp() {
- if b.Req != nil && b.Req.MultipartForm != nil {
- _ = b.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
- }
-}
-
func (b *Base) Tr(msg string, args ...any) template.HTML {
return b.Locale.Tr(msg, args...)
}
@@ -310,17 +169,28 @@ func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return b.Locale.TrN(cnt, key1, keyN, args...)
}
-func NewBaseContext(resp http.ResponseWriter, req *http.Request) (b *Base, closeFunc func()) {
- b = &Base{
- originCtx: req.Context(),
- Req: req,
- Resp: WrapResponseWriter(resp),
- Locale: middleware.Locale(resp, req),
- Data: middleware.GetContextData(req.Context()),
+func NewBaseContext(resp http.ResponseWriter, req *http.Request) *Base {
+ reqCtx := reqctx.FromContext(req.Context())
+ b := &Base{
+ RequestContext: reqCtx,
+
+ Req: req,
+ Resp: WrapResponseWriter(resp),
+ Locale: middleware.Locale(resp, req),
+ Data: reqCtx.GetData(),
}
b.Req = b.Req.WithContext(b)
- b.AppendContextValue(BaseContextKey, b)
- b.AppendContextValue(translation.ContextKey, b.Locale)
- b.AppendContextValue(httplib.RequestContextKey, b.Req)
- return b, b.cleanUp
+ reqCtx.SetContextValue(BaseContextKey, b)
+ reqCtx.SetContextValue(translation.ContextKey, b.Locale)
+ reqCtx.SetContextValue(httplib.RequestContextKey, b.Req)
+ return b
+}
+
+func NewBaseContextForTest(resp http.ResponseWriter, req *http.Request) *Base {
+ if !setting.IsInTesting {
+ panic("This function is only for testing")
+ }
+ ctx := reqctx.NewRequestContextForTest(req.Context())
+ *req = *req.WithContext(ctx)
+ return NewBaseContext(resp, req)
}
diff --git a/services/context/base_form.go b/services/context/base_form.go
new file mode 100644
index 0000000000..5b8cae9e99
--- /dev/null
+++ b/services/context/base_form.go
@@ -0,0 +1,77 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/util"
+)
+
+// FormString returns the first value matching the provided key in the form as a string
+func (b *Base) FormString(key string, def ...string) string {
+ s := b.Req.FormValue(key)
+ if s == "" {
+ s = util.OptionalArg(def)
+ }
+ return s
+}
+
+// FormStrings returns a string slice for the provided key from the form
+func (b *Base) FormStrings(key string) []string {
+ if b.Req.Form == nil {
+ if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
+ return nil
+ }
+ }
+ if v, ok := b.Req.Form[key]; ok {
+ return v
+ }
+ return nil
+}
+
+// FormTrim returns the first value for the provided key in the form as a space trimmed string
+func (b *Base) FormTrim(key string) string {
+ return strings.TrimSpace(b.Req.FormValue(key))
+}
+
+// FormInt returns the first value for the provided key in the form as an int
+func (b *Base) FormInt(key string) int {
+ v, _ := strconv.Atoi(b.Req.FormValue(key))
+ return v
+}
+
+// FormInt64 returns the first value for the provided key in the form as an int64
+func (b *Base) FormInt64(key string) int64 {
+ v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64)
+ return v
+}
+
+// FormBool returns true if the value for the provided key in the form is "1", "true" or "on"
+func (b *Base) FormBool(key string) bool {
+ s := b.Req.FormValue(key)
+ v, _ := strconv.ParseBool(s)
+ v = v || strings.EqualFold(s, "on")
+ return v
+}
+
+// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
+// for the provided key exists in the form else it returns optional.None[bool]()
+func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
+ value := b.Req.FormValue(key)
+ if len(value) == 0 {
+ return optional.None[bool]()
+ }
+ s := b.Req.FormValue(key)
+ v, _ := strconv.ParseBool(s)
+ v = v || strings.EqualFold(s, "on")
+ return optional.Some(v)
+}
+
+func (b *Base) SetFormString(key, value string) {
+ _ = b.Req.FormValue(key) // force parse form
+ b.Req.Form.Set(key, value)
+}
diff --git a/services/context/base_path.go b/services/context/base_path.go
new file mode 100644
index 0000000000..3678deaff9
--- /dev/null
+++ b/services/context/base_path.go
@@ -0,0 +1,47 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "net/url"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/go-chi/chi/v5"
+)
+
+// PathParam returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
+func (b *Base) PathParam(name string) string {
+ s, err := url.PathUnescape(b.PathParamRaw(name))
+ if err != nil && !setting.IsProd {
+ panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
+ }
+ return s
+}
+
+// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
+func (b *Base) PathParamRaw(name string) string {
+ if strings.HasPrefix(name, ":") {
+ setting.PanicInDevOrTesting("path param should not start with ':'")
+ name = name[1:]
+ }
+ return chi.URLParam(b.Req, name)
+}
+
+// PathParamInt64 returns the param in request path as int64
+func (b *Base) PathParamInt64(p string) int64 {
+ v, _ := strconv.ParseInt(b.PathParam(p), 10, 64)
+ return v
+}
+
+// SetPathParam set request path params into routes
+func (b *Base) SetPathParam(name, value string) {
+ if strings.HasPrefix(name, ":") {
+ setting.PanicInDevOrTesting("path param should not start with ':'")
+ name = name[1:]
+ }
+ chi.RouteContext(b).URLParams.Add(name, url.PathEscape(value))
+}
diff --git a/services/context/base_test.go b/services/context/base_test.go
index 823f20e00b..b936b76f58 100644
--- a/services/context/base_test.go
+++ b/services/context/base_test.go
@@ -14,6 +14,7 @@ import (
)
func TestRedirect(t *testing.T) {
+ setting.IsInTesting = true
req, _ := http.NewRequest("GET", "/", nil)
cases := []struct {
@@ -28,10 +29,9 @@ func TestRedirect(t *testing.T) {
}
for _, c := range cases {
resp := httptest.NewRecorder()
- b, cleanup := NewBaseContext(resp, req)
+ b := NewBaseContextForTest(resp, req)
resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String())
b.Redirect(c.url)
- cleanup()
has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy"
assert.Equal(t, c.keep, has, "url = %q", c.url)
}
@@ -39,9 +39,8 @@ func TestRedirect(t *testing.T) {
req, _ = http.NewRequest("GET", "/", nil)
resp := httptest.NewRecorder()
req.Header.Add("HX-Request", "true")
- b, cleanup := NewBaseContext(resp, req)
+ b := NewBaseContextForTest(resp, req)
b.Redirect("/other")
- cleanup()
assert.Equal(t, "/other", resp.Header().Get("HX-Redirect"))
assert.Equal(t, http.StatusNoContent, resp.Code)
}
diff --git a/services/context/context.go b/services/context/context.go
index 0d5429e366..5b16f9be98 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -18,7 +18,6 @@ import (
"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/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
@@ -153,14 +152,9 @@ func Contexter() func(next http.Handler) http.Handler {
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(resp, req)
- defer baseCleanUp()
+ base := NewBaseContext(resp, req)
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
-
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
- if setting.IsProd && !setting.IsInTesting {
- ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
- }
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
ctx.Data["Link"] = ctx.Link
@@ -168,23 +162,13 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.PageData = map[string]any{}
ctx.Data["PageData"] = ctx.PageData
- ctx.Base.AppendContextValue(WebContextKey, ctx)
- ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
-
+ ctx.Base.SetContextValue(WebContextKey, ctx)
ctx.Csrf = NewCSRFProtector(csrfOpts)
- // Get the last flash message from cookie
- lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
+ // get the last flash message from cookie
+ lastFlashCookie, lastFlashMsg := middleware.GetSiteCookieFlashMessage(ctx, ctx.Req, CookieNameFlash)
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
- // store last Flash message into the template data, to render it
- ctx.Data["Flash"] = &middleware.Flash{
- DataStore: ctx,
- Values: vals,
- ErrorMsg: vals.Get("error"),
- SuccessMsg: vals.Get("success"),
- InfoMsg: vals.Get("info"),
- WarningMsg: vals.Get("warning"),
- }
+ ctx.Data["Flash"] = lastFlashMsg // store last Flash message into the template data, to render it
}
// if there are new messages in the ctx.Flash, write them into cookie
diff --git a/services/context/context_model.go b/services/context/context_model.go
index 4f70aac516..3a1776102f 100644
--- a/services/context/context_model.go
+++ b/services/context/context_model.go
@@ -3,27 +3,7 @@
package context
-import (
- "code.gitea.io/gitea/models/unit"
-)
-
// IsUserSiteAdmin returns true if current user is a site admin
func (ctx *Context) IsUserSiteAdmin() bool {
return ctx.IsSigned && ctx.Doer.IsAdmin
}
-
-// IsUserRepoAdmin returns true if current user is admin in current repo
-func (ctx *Context) IsUserRepoAdmin() bool {
- return ctx.Repo.IsAdmin()
-}
-
-// IsUserRepoWriter returns true if current user has write privilege in current repo
-func (ctx *Context) IsUserRepoWriter(unitTypes []unit.Type) bool {
- for _, unitType := range unitTypes {
- if ctx.Repo.CanWrite(unitType) {
- return true
- }
- }
-
- return false
-}
diff --git a/services/context/context_response.go b/services/context/context_response.go
index 4c086ea9f5..c7044791eb 100644
--- a/services/context/context_response.go
+++ b/services/context/context_response.go
@@ -106,7 +106,7 @@ func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
}
// RenderToHTML renders the template content to a HTML string
-func (ctx *Context) RenderToHTML(name templates.TplName, data map[string]any) (template.HTML, error) {
+func (ctx *Context) RenderToHTML(name templates.TplName, data any) (template.HTML, error) {
var buf strings.Builder
err := ctx.Render.HTML(&buf, 0, name, data, ctx.TemplateContext)
return template.HTML(buf.String()), err
diff --git a/services/context/context_test.go b/services/context/context_test.go
index 984593398d..54044644f0 100644
--- a/services/context/context_test.go
+++ b/services/context/context_test.go
@@ -26,6 +26,7 @@ func TestRemoveSessionCookieHeader(t *testing.T) {
}
func TestRedirectToCurrentSite(t *testing.T) {
+ setting.IsInTesting = true
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
cases := []struct {
@@ -40,8 +41,7 @@ func TestRedirectToCurrentSite(t *testing.T) {
t.Run(c.location, func(t *testing.T) {
req := &http.Request{URL: &url.URL{Path: "/"}}
resp := httptest.NewRecorder()
- base, baseCleanUp := NewBaseContext(resp, req)
- defer baseCleanUp()
+ base := NewBaseContextForTest(resp, req)
ctx := NewWebContext(base, nil, nil)
ctx.RedirectToCurrentSite(c.location)
redirect := test.RedirectURL(resp)
diff --git a/services/context/org.go b/services/context/org.go
index bf482fa754..f4597a4ce1 100644
--- a/services/context/org.go
+++ b/services/context/org.go
@@ -40,7 +40,7 @@ func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
}
func GetOrganizationByParams(ctx *Context) {
- orgName := ctx.PathParam(":org")
+ orgName := ctx.PathParam("org")
var err error
@@ -63,6 +63,7 @@ func GetOrganizationByParams(ctx *Context) {
}
// HandleOrgAssignment handles organization assignment
+// args: requireMember, requireOwner, requireTeamMember, requireTeamAdmin
func HandleOrgAssignment(ctx *Context, args ...bool) {
var (
requireMember bool
@@ -220,7 +221,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Data["NumTeams"] = len(ctx.Org.Teams)
}
- teamName := ctx.PathParam(":team")
+ teamName := ctx.PathParam("team")
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {
@@ -269,6 +270,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
}
// OrgAssignment returns a middleware to handle organization assignment
+// args: requireMember, requireOwner, requireTeamMember, requireTeamAdmin
func OrgAssignment(args ...bool) func(ctx *Context) {
return func(ctx *Context) {
HandleOrgAssignment(ctx, args...)
diff --git a/services/context/package.go b/services/context/package.go
index 271b61e99c..e98e01acbb 100644
--- a/services/context/package.go
+++ b/services/context/package.go
@@ -153,12 +153,10 @@ func PackageContexter() func(next http.Handler) http.Handler {
renderer := templates.HTMLRenderer()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(resp, req)
- defer baseCleanUp()
-
+ base := NewBaseContext(resp, req)
// it is still needed when rendering 500 page in a package handler
ctx := NewWebContext(base, renderer, nil)
- ctx.Base.AppendContextValue(WebContextKey, ctx)
+ ctx.SetContextValue(WebContextKey, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
diff --git a/services/context/pagination.go b/services/context/pagination.go
index 42117cf96d..d33dd217d0 100644
--- a/services/context/pagination.go
+++ b/services/context/pagination.go
@@ -27,19 +27,13 @@ func NewPagination(total, pagingNum, current, numPages int) *Pagination {
return p
}
-// AddParamString adds a string parameter directly
-func (p *Pagination) AddParamString(key, value string) {
- urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(key), url.QueryEscape(value))
- p.urlParams = append(p.urlParams, urlParam)
-}
-
func (p *Pagination) AddParamFromRequest(req *http.Request) {
for key, values := range req.URL.Query() {
- if key == "page" || len(values) == 0 {
+ if key == "page" || len(values) == 0 || (len(values) == 1 && values[0] == "") {
continue
}
for _, value := range values {
- urlParam := fmt.Sprintf("%s=%v", key, url.QueryEscape(value))
+ urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(key), url.QueryEscape(value))
p.urlParams = append(p.urlParams, urlParam)
}
}
@@ -49,17 +43,3 @@ func (p *Pagination) AddParamFromRequest(req *http.Request) {
func (p *Pagination) GetParams() template.URL {
return template.URL(strings.Join(p.urlParams, "&"))
}
-
-// SetDefaultParams sets common pagination params that are often used
-func (p *Pagination) SetDefaultParams(ctx *Context) {
- if v, ok := ctx.Data["SortType"].(string); ok {
- p.AddParamString("sort", v)
- }
- if v, ok := ctx.Data["Keyword"].(string); ok {
- p.AddParamString("q", v)
- }
- if v, ok := ctx.Data["IsFuzzy"].(bool); ok {
- p.AddParamString("fuzzy", fmt.Sprint(v))
- }
- // do not add any more uncommon params here!
-}
diff --git a/services/context/permission.go b/services/context/permission.go
index 9338587257..0d69ccc4a4 100644
--- a/services/context/permission.go
+++ b/services/context/permission.go
@@ -9,31 +9,20 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/log"
)
// RequireRepoAdmin returns a middleware for requiring repository admin permission
func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
+ ctx.NotFound("RequireRepoAdmin denies the request", nil)
return
}
}
}
-// RequireRepoWriter returns a middleware for requiring repository write to the specify unitType
-func RequireRepoWriter(unitType unit.Type) func(ctx *Context) {
- return func(ctx *Context) {
- if !ctx.Repo.CanWrite(unitType) {
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
- return
- }
- }
-}
-
-// CanEnableEditor checks if the user is allowed to write to the branch of the repo
-func CanEnableEditor() func(ctx *Context) {
+// CanWriteToBranch checks if the user is allowed to write to the branch of the repo
+func CanWriteToBranch() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.NotFound("CanWriteToBranch denies permission", nil)
@@ -42,75 +31,30 @@ func CanEnableEditor() func(ctx *Context) {
}
}
-// RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission
-func RequireRepoWriterOr(unitTypes ...unit.Type) 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
}
}
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
+ ctx.NotFound("RequireUnitWriter denies the request", nil)
}
}
-// RequireRepoReader returns a middleware for requiring repository read to the specify unitType
-func RequireRepoReader(unitType unit.Type) func(ctx *Context) {
- return func(ctx *Context) {
- if !ctx.Repo.CanRead(unitType) {
- if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
- return
- }
- if log.IsTrace() {
- if ctx.IsSigned {
- log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
- "User in Repo has Permissions: %-+v",
- ctx.Doer,
- unitType,
- ctx.Repo.Repository,
- ctx.Repo.Permission)
- } else {
- log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
- "Anonymous user in Repo has Permissions: %-+v",
- unitType,
- ctx.Repo.Repository,
- ctx.Repo.Permission)
- }
- }
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
- return
- }
- }
-}
-
-// RequireRepoReaderOr returns a middleware for requiring repository write to one of the unit permission
-func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) {
+// RequireUnitReader returns a middleware for requiring repository write to one of the unit permission
+func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) {
return
}
- }
- if log.IsTrace() {
- var format string
- var args []any
- if ctx.IsSigned {
- format = "Permission Denied: User %-v cannot read ["
- args = append(args, ctx.Doer)
- } else {
- format = "Permission Denied: Anonymous user cannot read ["
- }
- for _, unit := range unitTypes {
- format += "%-v, "
- args = append(args, unit)
+ if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
+ return
}
-
- format = format[:len(format)-2] + "] in Repo %-v\n" +
- "User in Repo has Permissions: %-+v"
- args = append(args, ctx.Repo.Repository, ctx.Repo.Permission)
- log.Trace(format, args...)
}
- ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
+ ctx.NotFound("RequireUnitReader denies the request", nil)
}
}
diff --git a/services/context/private.go b/services/context/private.go
index 8b41949f60..51857da8fe 100644
--- a/services/context/private.go
+++ b/services/context/private.go
@@ -64,11 +64,9 @@ func GetPrivateContext(req *http.Request) *PrivateContext {
func PrivateContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(w, req)
+ base := NewBaseContext(w, req)
ctx := &PrivateContext{Base: base}
- defer baseCleanUp()
- ctx.Base.AppendContextValue(privateContextKey, ctx)
-
+ ctx.SetContextValue(privateContextKey, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
@@ -78,8 +76,15 @@ func PrivateContexter() func(http.Handler) http.Handler {
// This function should be used when there is a need for work to continue even if the request has been cancelled.
// Primarily this affects hook/post-receive and hook/proc-receive both of which need to continue working even if
// 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().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
- return cancel
+func OverrideContext() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ // 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)
+ defer finished()
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
}
diff --git a/services/context/repo.go b/services/context/repo.go
index e96916ca42..1cb35b9b83 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -46,22 +46,21 @@ type PullRequest struct {
// Repository contains information to operate a repository
type Repository struct {
access_model.Permission
- IsWatching bool
- IsViewBranch bool
- IsViewTag bool
- IsViewCommit bool
- Repository *repo_model.Repository
- Owner *user_model.User
+
+ Repository *repo_model.Repository
+ Owner *user_model.User
+
+ RepoLink string
+ GitRepo *git.Repository
+
+ // RefFullName is the full ref name that the user is viewing
+ RefFullName git.RefName
+ BranchName string // it is the RefFullName's short name if its type is "branch"
+ TreePath string
+
+ // Commit it is always set to the commit for the branch or tag, or just the commit that the user is viewing
Commit *git.Commit
- Tag *git.Tag
- GitRepo *git.Repository
- RefName string
- BranchName string
- TagName string
- TreePath string
CommitID string
- RepoLink string
- CloneLink repo_model.CloneLink
CommitsCount int64
PullRequest *PullRequest
@@ -74,7 +73,7 @@ func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.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.IsViewBranch && r.CanWriteToBranch(ctx, user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
+ 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.
@@ -149,7 +148,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use
}, err
}
-// CanUseTimetracker returns whether or not a user can use the timetracker.
+// CanUseTimetracker returns whether a user can use the timetracker.
func (r *Repository) CanUseTimetracker(ctx context.Context, issue *issues_model.Issue, user *user_model.User) bool {
// Checking for following:
// 1. Is timetracker enabled
@@ -169,15 +168,9 @@ func (r *Repository) GetCommitsCount() (int64, error) {
if r.Commit == nil {
return 0, nil
}
- var contextName string
- if r.IsViewBranch {
- contextName = r.BranchName
- } else if r.IsViewTag {
- contextName = r.TagName
- } else {
- contextName = r.CommitID
- }
- return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, r.IsViewBranch || r.IsViewTag), func() (int64, error) {
+ contextName := r.RefFullName.ShortName()
+ isRef := r.RefFullName.IsBranch() || r.RefFullName.IsTag()
+ return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, isRef), func() (int64, error) {
return r.Commit.CommitsCount()
})
}
@@ -199,33 +192,13 @@ func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool,
})
}
-// BranchNameSubURL sub-URL for the BranchName field
-func (r *Repository) BranchNameSubURL() string {
- switch {
- case r.IsViewBranch:
- return "branch/" + util.PathEscapeSegments(r.BranchName)
- case r.IsViewTag:
- return "tag/" + util.PathEscapeSegments(r.TagName)
- case r.IsViewCommit:
- return "commit/" + util.PathEscapeSegments(r.CommitID)
- }
- log.Error("Unknown view type for repo: %v", r)
- return ""
-}
-
-// FileExists returns true if a file exists in the given repo branch
-func (r *Repository) FileExists(path, branch string) (bool, error) {
- if branch == "" {
- branch = r.Repository.DefaultBranch
- }
- commit, err := r.GitRepo.GetBranchCommit(branch)
- if err != nil {
- return false, err
- }
- if _, err := commit.GetTreeEntryByPath(path); err != nil {
- return false, err
- }
- return true, nil
+// RefTypeNameSubURL makes a sub-url for the current ref (branch/tag/commit) field, for example:
+// * "branch/master"
+// * "tag/v1.0.0"
+// * "commit/123456"
+// It is usually used to construct a link like ".../src/{{RefTypeNameSubURL}}/{{PathEscapeSegments TreePath}}"
+func (r *Repository) RefTypeNameSubURL() string {
+ return r.RefFullName.RefWebLinkPath()
}
// GetEditorconfig returns the .editorconfig definition if found in the
@@ -316,8 +289,8 @@ func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
func EarlyResponseForGoGetMeta(ctx *Context) {
- username := ctx.PathParam(":username")
- reponame := strings.TrimSuffix(ctx.PathParam(":reponame"), ".git")
+ username := ctx.PathParam("username")
+ reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
if username == "" || reponame == "" {
ctx.PlainText(http.StatusBadRequest, "invalid repository path")
return
@@ -325,9 +298,9 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
var cloneURL string
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
- cloneURL = repo_model.ComposeSSHCloneURL(username, reponame)
+ cloneURL = repo_model.ComposeSSHCloneURL(ctx.Doer, username, reponame)
} else {
- cloneURL = repo_model.ComposeHTTPSCloneURL(username, reponame)
+ cloneURL = repo_model.ComposeHTTPSCloneURL(ctx, username, reponame)
}
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL)
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
@@ -336,8 +309,8 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
// RedirectToRepo redirect to a differently-named repository
func RedirectToRepo(ctx *Base, redirectRepoID int64) {
- ownerName := ctx.PathParam(":username")
- previousRepoName := ctx.PathParam(":reponame")
+ ownerName := ctx.PathParam("username")
+ previousRepoName := ctx.PathParam("reponame")
repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID)
if err != nil {
@@ -397,39 +370,33 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
}
// RepoAssignment returns a middleware to handle repository assignment
-func RepoAssignment(ctx *Context) context.CancelFunc {
- if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
- // FIXME: it should panic in dev/test modes to have a clear behavior
- log.Trace("RepoAssignment was exec already, skipping second call ...")
- return nil
+func RepoAssignment(ctx *Context) {
+ if ctx.Data["Repository"] != nil {
+ setting.PanicInDevOrTesting("RepoAssignment should not be executed twice")
}
- ctx.Data["repoAssignmentExecuted"] = true
-
- var (
- owner *user_model.User
- err error
- )
- userName := ctx.PathParam(":username")
- repoName := ctx.PathParam(":reponame")
+ var err error
+ userName := ctx.PathParam("username")
+ repoName := ctx.PathParam("reponame")
repoName = strings.TrimSuffix(repoName, ".git")
if setting.Other.EnableFeed {
+ ctx.Data["EnableFeed"] = true
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.Doer.LowerName == strings.ToLower(userName) {
- owner = ctx.Doer
+ ctx.Repo.Owner = ctx.Doer
} else {
- owner, err = user_model.GetUserByName(ctx, userName)
+ ctx.Repo.Owner, err = user_model.GetUserByName(ctx, userName)
if err != nil {
if user_model.IsErrUserNotExist(err) {
// go-get does not support redirects
// https://github.com/golang/go/issues/19760
if ctx.FormString("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
- return nil
+ return
}
if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
@@ -442,19 +409,17 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
} else {
ctx.ServerError("GetUserByName", err)
}
- return nil
+ return
}
}
- ctx.Repo.Owner = owner
- ctx.ContextUser = owner
+ ctx.ContextUser = ctx.Repo.Owner
ctx.Data["ContextUser"] = ctx.ContextUser
- 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.PathParam(":reponame")
+ originalRepoName := ctx.PathParam("reponame")
redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
redirectPath := strings.Replace(
@@ -467,20 +432,20 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
redirectPath += "?" + ctx.Req.URL.RawQuery
}
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
- return nil
+ return
}
// Get repository.
- repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName)
+ repo, err := repo_model.GetRepositoryByName(ctx, ctx.Repo.Owner.ID, repoName)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
- redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, repoName)
+ redirectRepoID, err := repo_model.LookupRedirect(ctx, ctx.Repo.Owner.ID, repoName)
if err == nil {
RedirectToRepo(ctx.Base, redirectRepoID)
} else if repo_model.IsErrRedirectNotExist(err) {
if ctx.FormString("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
- return nil
+ return
}
ctx.NotFound("GetRepositoryByName", nil)
} else {
@@ -489,23 +454,18 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
} else {
ctx.ServerError("GetRepositoryByName", err)
}
- return nil
+ return
}
- repo.Owner = owner
+ repo.Owner = ctx.Repo.Owner
repoAssignment(ctx, repo)
if ctx.Written() {
- return nil
+ return
}
ctx.Repo.RepoLink = repo.Link()
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
- ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
-
- if setting.Other.EnableFeed {
- ctx.Data["EnableFeed"] = true
- ctx.Data["FeedURL"] = ctx.Repo.RepoLink
- }
+ ctx.Data["FeedURL"] = ctx.Repo.RepoLink
unit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeExternalTracker)
if err == nil {
@@ -520,7 +480,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
})
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
- return nil
+ return
}
ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
@@ -529,15 +489,12 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
})
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
- return nil
+ return
}
- ctx.Data["Title"] = owner.Name + "/" + repo.Name
+ ctx.Data["Title"] = repo.Owner.Name + "/" + repo.Name
ctx.Data["Repository"] = repo
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
- ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
- ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
- ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode)
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues)
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests)
@@ -546,14 +503,14 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("CanUserForkRepo", err)
- return nil
+ return
}
ctx.Data["CanSignedUserFork"] = canSignedUserFork
userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetForksByUserAndOrgs", err)
- return nil
+ return
}
ctx.Data["UserAndOrgForks"] = userAndOrgForks
@@ -562,7 +519,7 @@ func RepoAssignment(ctx *Context) 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["RepoCloneLink"] = repo.CloneLink()
+ ctx.Data["RepoCloneLink"] = repo.CloneLink(ctx, ctx.Doer)
cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit
cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous)
@@ -587,14 +544,14 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
if repo.IsFork {
RetrieveBaseRepo(ctx, repo)
if ctx.Written() {
- return nil
+ return
}
}
if repo.IsGenerated() {
RetrieveTemplateRepo(ctx, repo)
if ctx.Written() {
- return nil
+ return
}
}
@@ -605,45 +562,36 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
// Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
- return nil
+ return
+ }
+
+ if ctx.Repo.GitRepo != nil {
+ setting.PanicInDevOrTesting("RepoAssignment: GitRepo should be nil")
+ _ = ctx.Repo.GitRepo.Close()
+ ctx.Repo.GitRepo = nil
}
- gitRepo, err := gitrepo.OpenRepository(ctx, repo)
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
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)
ctx.Repo.Repository.MarkAsBrokenEmpty()
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
// Only allow access to base of repo or settings
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
- return nil
+ return
}
ctx.ServerError("RepoAssignment Invalid repo "+repo.FullName(), err)
- return nil
- }
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
- }
- ctx.Repo.GitRepo = gitRepo
-
- // We opened it, we should close it
- cancel := 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
}
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
- return cancel
+ return
}
branchOpts := git_model.FindBranchOptions{
@@ -654,7 +602,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
if err != nil {
ctx.ServerError("CountBranches", err)
- return cancel
+ return
}
// non-empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
@@ -662,28 +610,12 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
- return cancel
+ return
}
}
ctx.Data["BranchesCount"] = branchesTotal
- // If no branch is set in the request URL, try to guess a default one.
- if len(ctx.Repo.BranchName) == 0 {
- if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
- ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
- } else {
- ctx.Repo.BranchName, _ = gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository)
- if ctx.Repo.BranchName == "" {
- // If it still can't get a default branch, fall back to default branch from setting.
- // Something might be wrong. Either site admin should fix the repo sync or Gitea should fix a potential bug.
- ctx.Repo.BranchName = setting.Repository.DefaultBranch
- }
- }
- ctx.Repo.RefName = ctx.Repo.BranchName
- }
- ctx.Data["BranchName"] = ctx.Repo.BranchName
-
// 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, ctx.Doer.ID, ctx.Repo.Repository.ID))
@@ -711,48 +643,34 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetPendingRepositoryTransfer", err)
- return cancel
+ return
}
if err := repoTransfer.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadRecipient", err)
- return cancel
+ return
}
ctx.Data["RepoTransfer"] = repoTransfer
if ctx.Doer != nil {
- ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer)
+ ctx.Data["CanUserAcceptOrRejectTransfer"] = repoTransfer.CanUserAcceptOrRejectTransfer(ctx, ctx.Doer)
}
}
if ctx.FormString("go-get") == "1" {
- ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, owner.Name, repo.Name)
+ ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, repo.Owner.Name, repo.Name)
fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
}
- return cancel
}
-// RepoRefType type of repo reference
-type RepoRefType int
-
-const (
- // RepoRefUnknown is for legacy support, makes the code to "guess" the ref type
- RepoRefUnknown RepoRefType = iota
- RepoRefBranch
- RepoRefTag
- RepoRefCommit
- RepoRefBlob
-)
-
const headRefName = "HEAD"
-// RepoRef handles repository reference names when the ref name is not
-// explicitly given
-func RepoRef() func(*Context) context.CancelFunc {
- // since no ref name is explicitly specified, ok to just use branch
- return RepoRefByType(RepoRefBranch)
+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 {
@@ -768,37 +686,29 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
return ""
}
-func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
- extraRef := util.OptionalArg(optionalExtraRef)
- reqPath := ctx.PathParam("*")
- reqPath = path.Join(extraRef, reqPath)
-
- if refName := getRefName(ctx, repo, RepoRefBranch); refName != "" {
- return refName, RepoRefBranch
+func getRefNameLegacy(ctx *Base, repo *Repository, reqPath, extraRef string) (refName string, refType git.RefType, fallbackDefaultBranch bool) {
+ reqRefPath := path.Join(extraRef, reqPath)
+ reqRefPathParts := strings.Split(reqRefPath, "/")
+ if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeBranch); refName != "" {
+ return refName, git.RefTypeBranch, false
}
- if refName := getRefName(ctx, repo, RepoRefTag); refName != "" {
- return refName, RepoRefTag
+ if refName := getRefName(ctx, repo, reqRefPath, git.RefTypeTag); refName != "" {
+ return refName, git.RefTypeTag, false
}
-
- // For legacy support only full commit sha
- parts := strings.Split(reqPath, "/")
- if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
+ if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), reqRefPathParts[0]) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
- repo.TreePath = strings.Join(parts[1:], "/")
- return parts[0], RepoRefCommit
- }
-
- if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
- return refName, RepoRefBlob
+ repo.TreePath = strings.Join(reqRefPathParts[1:], "/")
+ return reqRefPathParts[0], git.RefTypeCommit, false
}
+ // FIXME: the old code falls back to default branch if "ref" doesn't exist, there could be an edge case:
+ // "README?ref=no-such" would read the README file from the default branch, but the user might expect a 404
repo.TreePath = reqPath
- return repo.Repository.DefaultBranch, RepoRefBranch
+ return repo.Repository.DefaultBranch, git.RefTypeBranch, true
}
-func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
- path := ctx.PathParam("*")
- switch pathType {
- case RepoRefBranch:
+func getRefName(ctx *Base, repo *Repository, path string, refType git.RefType) string {
+ switch refType {
+ case git.RefTypeBranch:
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
if len(ref) == 0 {
// check if ref is HEAD
@@ -828,9 +738,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
}
return ref
- case RepoRefTag:
+ case git.RefTypeTag:
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
- case RepoRefCommit:
+ case git.RefTypeCommit:
parts := strings.Split(path, "/")
if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
@@ -847,182 +757,187 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
repo.TreePath = strings.Join(parts[1:], "/")
return commit.ID.String()
}
- case RepoRefBlob:
- _, err := repo.GitRepo.GetBlob(path)
- if err != nil {
- return ""
- }
- return path
default:
- panic(fmt.Sprintf("Unrecognized path type: %v", pathType))
+ panic(fmt.Sprintf("Unrecognized ref type: %v", refType))
}
return ""
}
-type RepoRefByTypeOptions struct {
- IgnoreNotExistErr bool
+func repoRefFullName(typ git.RefType, shortName string) git.RefName {
+ switch typ {
+ case git.RefTypeBranch:
+ return git.RefNameFromBranch(shortName)
+ case git.RefTypeTag:
+ return git.RefNameFromTag(shortName)
+ case git.RefTypeCommit:
+ return git.RefNameFromCommit(shortName)
+ default:
+ setting.PanicInDevOrTesting("Unknown RepoRefType: %v", typ)
+ return git.RefNameFromBranch("main") // just a dummy result, it shouldn't happen
+ }
+}
+
+func RepoRefByDefaultBranch() func(*Context) {
+ return func(ctx *Context) {
+ ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch)
+ ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
+ ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
+ ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount()
+ ctx.Data["RefFullName"] = ctx.Repo.RefFullName
+ ctx.Data["BranchName"] = ctx.Repo.BranchName
+ ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
+ }
}
// RepoRefByType handles repository reference name for a specific type
// of repository reference
-func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) context.CancelFunc {
- opt := util.OptionalArg(opts)
- return func(ctx *Context) (cancel context.CancelFunc) {
+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"
+ }
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
// assume the user is viewing the (non-existent) default branch
- ctx.Repo.IsViewBranch = true
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
+ ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.BranchName)
+ // these variables are used by the template to "add/upload" new files
+ ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["TreePath"] = ""
- return nil
- }
-
- var (
- refName string
- err error
- )
-
- if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
- if err != nil {
- ctx.ServerError(fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
- return nil
- }
- // We opened it, we should close it
- cancel = 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
}
// Get default branch.
- if len(ctx.PathParam("*")) == 0 {
- refName = ctx.Repo.Repository.DefaultBranch
- if !ctx.Repo.GitRepo.IsBranchExist(refName) {
+ var refShortName string
+ reqPath := ctx.PathParam("*")
+ if reqPath == "" {
+ refShortName = ctx.Repo.Repository.DefaultBranch
+ if !ctx.Repo.GitRepo.IsBranchExist(refShortName) {
brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
if err == nil && len(brs) != 0 {
- refName = brs[0].Name
+ refShortName = brs[0].Name
} else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
- ctx.Repo.Repository.MarkAsBrokenEmpty()
} else {
log.Error("GetBranches error: %v", err)
- ctx.Repo.Repository.MarkAsBrokenEmpty()
}
}
- ctx.Repo.RefName = refName
- ctx.Repo.BranchName = refName
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
+ ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
+ ctx.Repo.BranchName = refShortName
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refShortName)
if err == nil {
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if strings.Contains(err.Error(), "fatal: not a git repository") || strings.Contains(err.Error(), "object does not exist") {
// if the repository is broken, we can continue to the handler code, to show "Settings -> Delete Repository" for end users
log.Error("GetBranchCommit: %v", err)
- ctx.Repo.Repository.MarkAsBrokenEmpty()
} else {
ctx.ServerError("GetBranchCommit", err)
- return cancel
+ return
}
- ctx.Repo.IsViewBranch = true
- } else {
- guessLegacyPath := refType == RepoRefUnknown
+ } else { // there is a path in request
+ guessLegacyPath := refType == ""
+ fallbackDefaultBranch := false
if guessLegacyPath {
- refName, refType = getRefNameLegacy(ctx.Base, ctx.Repo)
+ refShortName, refType, fallbackDefaultBranch = getRefNameLegacy(ctx.Base, ctx.Repo, reqPath, "")
} else {
- refName = getRefName(ctx.Base, ctx.Repo, refType)
+ refShortName = getRefName(ctx.Base, ctx.Repo, reqPath, refType)
}
- ctx.Repo.RefName = refName
+ ctx.Repo.RefFullName = repoRefFullName(refType, refShortName)
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
if isRenamedBranch && has {
renamedBranchName := ctx.Data["RenamedBranchName"].(string)
- ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName))
- link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1)
+ ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refShortName, renamedBranchName))
+ link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refShortName), util.PathEscapeSegments(renamedBranchName), 1)
ctx.Redirect(link)
- return cancel
+ return
}
- if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refName) {
- ctx.Repo.IsViewBranch = true
- ctx.Repo.BranchName = refName
+ if refType == git.RefTypeBranch && ctx.Repo.GitRepo.IsBranchExist(refShortName) {
+ ctx.Repo.BranchName = refShortName
+ ctx.Repo.RefFullName = git.RefNameFromBranch(refShortName)
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refShortName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
- return cancel
+ return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refName) {
- ctx.Repo.IsViewTag = true
- ctx.Repo.TagName = refName
+ } else if refType == git.RefTypeTag && ctx.Repo.GitRepo.IsTagExist(refShortName) {
+ ctx.Repo.RefFullName = git.RefNameFromTag(refShortName)
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refShortName)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetTagCommit", err)
- return cancel
+ return
}
ctx.ServerError("GetTagCommit", err)
- return cancel
+ return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
- ctx.Repo.IsViewCommit = true
- ctx.Repo.CommitID = refName
+ } else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refShortName, 7) {
+ ctx.Repo.RefFullName = git.RefNameFromCommit(refShortName)
+ ctx.Repo.CommitID = refShortName
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refShortName)
if err != nil {
ctx.NotFound("GetCommit", err)
- return cancel
+ return
}
// If short commit ID add canonical link header
- if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
- canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
+ if len(refShortName) < ctx.Repo.GetObjectFormat().FullLength() {
+ canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refShortName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL))
}
} else {
- if opt.IgnoreNotExistErr {
- return cancel
- }
- ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
- return cancel
+ ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refShortName))
+ return
}
if guessLegacyPath {
// redirect from old URL scheme to new URL scheme
- prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.PathParam("*"))), strings.ToLower(ctx.Repo.RepoLink))
- redirect := path.Join(
- ctx.Repo.RepoLink,
- util.PathEscapeSegments(prefix),
- ctx.Repo.BranchNameSubURL(),
- util.PathEscapeSegments(ctx.Repo.TreePath))
+ // * /user2/repo1/commits/master => /user2/repo1/commits/branch/master
+ // * /user2/repo1/src/master => /user2/repo1/src/branch/master
+ // * /user2/repo1/src/README.md => /user2/repo1/src/branch/master/README.md (fallback to default branch)
+ var redirect string
+ refSubPath := "src"
+ // remove the "/subpath/owner/repo/" prefix, the names are case-insensitive
+ remainingLowerPath, cut := strings.CutPrefix(setting.AppSubURL+strings.ToLower(ctx.Req.URL.Path), strings.ToLower(ctx.Repo.RepoLink)+"/")
+ if cut {
+ refSubPath, _, _ = strings.Cut(remainingLowerPath, "/") // it could be "src" or "commits"
+ }
+ if fallbackDefaultBranch {
+ redirect = fmt.Sprintf("%s/%s/%s/%s/%s", ctx.Repo.RepoLink, refSubPath, refType, util.PathEscapeSegments(refShortName), ctx.PathParamRaw("*"))
+ } else {
+ redirect = fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, refSubPath, refType, ctx.PathParamRaw("*"))
+ }
+ if ctx.Req.URL.RawQuery != "" {
+ redirect += "?" + ctx.Req.URL.RawQuery
+ }
ctx.Redirect(redirect)
- return cancel
+ return
}
}
+ ctx.Data["RefFullName"] = ctx.Repo.RefFullName
+ ctx.Data["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
+ ctx.Data["TreePath"] = ctx.Repo.TreePath
+
ctx.Data["BranchName"] = ctx.Repo.BranchName
- ctx.Data["RefName"] = ctx.Repo.RefName
- ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
- ctx.Data["TagName"] = ctx.Repo.TagName
+
ctx.Data["CommitID"] = ctx.Repo.CommitID
- ctx.Data["TreePath"] = ctx.Repo.TreePath
- ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
- ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
- ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
+
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
- return cancel
+ return
}
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
-
- return cancel
}
}
diff --git a/services/context/response.go b/services/context/response.go
index 2f271f211b..c7368ebc6f 100644
--- a/services/context/response.go
+++ b/services/context/response.go
@@ -11,31 +11,29 @@ import (
// ResponseWriter represents a response writer for HTTP
type ResponseWriter interface {
- http.ResponseWriter
- http.Flusher
- web_types.ResponseStatusProvider
-
- Before(func(ResponseWriter))
+ http.ResponseWriter // provides Header/Write/WriteHeader
+ http.Flusher // provides Flush
+ web_types.ResponseStatusProvider // provides WrittenStatus
- Status() int // used by access logger template
- Size() int // used by access logger template
+ Before(fn func(ResponseWriter))
+ WrittenSize() int
}
-var _ ResponseWriter = &Response{}
+var _ ResponseWriter = (*Response)(nil)
// Response represents a response
type Response struct {
http.ResponseWriter
written int
status int
- befores []func(ResponseWriter)
+ beforeFuncs []func(ResponseWriter)
beforeExecuted bool
}
// Write writes bytes to HTTP endpoint
func (r *Response) Write(bs []byte) (int, error) {
if !r.beforeExecuted {
- for _, before := range r.befores {
+ for _, before := range r.beforeFuncs {
before(r)
}
r.beforeExecuted = true
@@ -51,18 +49,14 @@ func (r *Response) Write(bs []byte) (int, error) {
return size, nil
}
-func (r *Response) Status() int {
- return r.status
-}
-
-func (r *Response) Size() int {
+func (r *Response) WrittenSize() int {
return r.written
}
// WriteHeader write status code
func (r *Response) WriteHeader(statusCode int) {
if !r.beforeExecuted {
- for _, before := range r.befores {
+ for _, before := range r.beforeFuncs {
before(r)
}
r.beforeExecuted = true
@@ -87,17 +81,13 @@ func (r *Response) WrittenStatus() int {
// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
-func (r *Response) Before(f func(ResponseWriter)) {
- r.befores = append(r.befores, f)
+func (r *Response) Before(fn func(ResponseWriter)) {
+ r.beforeFuncs = append(r.beforeFuncs, fn)
}
func WrapResponseWriter(resp http.ResponseWriter) *Response {
if v, ok := resp.(*Response); ok {
return v
}
- return &Response{
- ResponseWriter: resp,
- status: 0,
- befores: make([]func(ResponseWriter), 0),
- }
+ return &Response{ResponseWriter: resp}
}
diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go
index cefd13ebb6..da4370a433 100644
--- a/services/context/upload/upload.go
+++ b/services/context/upload/upload.go
@@ -97,8 +97,8 @@ func AddUploadContext(ctx *context.Context, uploadType string) {
} else if uploadType == "comment" {
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
- if len(ctx.PathParam(":index")) > 0 {
- ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam(":index")) + "/attachments"
+ if len(ctx.PathParam("index")) > 0 {
+ ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam("index")) + "/attachments"
} else {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
}
diff --git a/services/context/user.go b/services/context/user.go
index b0e855e923..dbc35e198d 100644
--- a/services/context/user.go
+++ b/services/context/user.go
@@ -33,7 +33,7 @@ func UserAssignmentWeb() func(ctx *Context) {
// UserIDAssignmentAPI returns a middleware to handle context-user assignment for api routes
func UserIDAssignmentAPI() func(ctx *APIContext) {
return func(ctx *APIContext) {
- userID := ctx.PathParamInt64(":user-id")
+ userID := ctx.PathParamInt64("user-id")
if ctx.IsSigned && ctx.Doer.ID == userID {
ctx.ContextUser = ctx.Doer
@@ -59,7 +59,7 @@ func UserAssignmentAPI() func(ctx *APIContext) {
}
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) {
- username := ctx.PathParam(":username")
+ username := ctx.PathParam("username")
if doer != nil && doer.LowerName == strings.ToLower(username) {
contextUser = doer