Skip to content

Commit aef18ff

Browse files
authored
Merge branch 'main' into fix-ref-usage
2 parents 8576c5b + 39de295 commit aef18ff

File tree

10 files changed

+268
-94
lines changed

10 files changed

+268
-94
lines changed

models/git/branch.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
167167
BranchName: branchName,
168168
}
169169
}
170+
// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
171+
// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
172+
// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
170173
return &branch, nil
171174
}
172175

options/locale/locale_en-US.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,7 @@ pulls.upstream_diverging_prompt_behind_1 = This branch is %[1]d commit behind %[
19531953
pulls.upstream_diverging_prompt_behind_n = This branch is %[1]d commits behind %[2]s
19541954
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
19551955
pulls.upstream_diverging_merge = Sync fork
1956-
pulls.upstream_diverging_merge_confirm = Would you like to merge base repository's default branch onto this repository's branch %s?
1956+
pulls.upstream_diverging_merge_confirm = Would you like to merge "%[1]s" onto "%[2]s"?
19571957

19581958
pull.deleted_branch = (deleted):%s
19591959
pull.agit_documentation = Review documentation about AGit

routers/common/middleware.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,18 @@ func ProtocolMiddlewares() (handlers []any) {
4343

4444
func RequestContextHandler() func(h http.Handler) http.Handler {
4545
return func(next http.Handler) http.Handler {
46-
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
47-
profDesc := fmt.Sprintf("%s: %s", req.Method, req.RequestURI)
46+
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
47+
// this response writer might not be the same as the one in context.Base.Resp
48+
// because there might be a "gzip writer" in the middle, so the "written size" here is the compressed size
49+
respWriter := context.WrapResponseWriter(respOrig)
50+
51+
profDesc := fmt.Sprintf("HTTP: %s %s", req.Method, req.RequestURI)
4852
ctx, finished := reqctx.NewRequestContext(req.Context(), profDesc)
4953
defer finished()
5054

5155
defer func() {
5256
if err := recover(); err != nil {
53-
RenderPanicErrorPage(resp, req, err) // it should never panic
57+
RenderPanicErrorPage(respWriter, req, err) // it should never panic
5458
}
5559
}()
5660

@@ -62,7 +66,7 @@ func RequestContextHandler() func(h http.Handler) http.Handler {
6266
_ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
6367
}
6468
})
65-
next.ServeHTTP(context.WrapResponseWriter(resp), req)
69+
next.ServeHTTP(respWriter, req)
6670
})
6771
}
6872
}

services/context/access_log.go

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import (
1818
"code.gitea.io/gitea/modules/web/middleware"
1919
)
2020

21-
type routerLoggerOptions struct {
22-
req *http.Request
21+
type accessLoggerTmplData struct {
2322
Identity *string
2423
Start *time.Time
25-
ResponseWriter http.ResponseWriter
26-
Ctx map[string]any
27-
RequestID *string
24+
ResponseWriter struct {
25+
Status, Size int
26+
}
27+
Ctx map[string]any
28+
RequestID *string
2829
}
2930

3031
const keyOfRequestIDInTemplate = ".RequestID"
@@ -51,51 +52,65 @@ func parseRequestIDFromRequestHeader(req *http.Request) string {
5152
return requestID
5253
}
5354

55+
type accessLogRecorder struct {
56+
logger log.BaseLogger
57+
logTemplate *template.Template
58+
needRequestID bool
59+
}
60+
61+
func (lr *accessLogRecorder) record(start time.Time, respWriter ResponseWriter, req *http.Request) {
62+
var requestID string
63+
if lr.needRequestID {
64+
requestID = parseRequestIDFromRequestHeader(req)
65+
}
66+
67+
reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
68+
if err != nil {
69+
reqHost = req.RemoteAddr
70+
}
71+
72+
identity := "-"
73+
data := middleware.GetContextData(req.Context())
74+
if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
75+
identity = signedUser.Name
76+
}
77+
buf := bytes.NewBuffer([]byte{})
78+
tmplData := accessLoggerTmplData{
79+
Identity: &identity,
80+
Start: &start,
81+
Ctx: map[string]any{
82+
"RemoteAddr": req.RemoteAddr,
83+
"RemoteHost": reqHost,
84+
"Req": req,
85+
},
86+
RequestID: &requestID,
87+
}
88+
tmplData.ResponseWriter.Status = respWriter.Status()
89+
tmplData.ResponseWriter.Size = respWriter.WrittenSize()
90+
err = lr.logTemplate.Execute(buf, tmplData)
91+
if err != nil {
92+
log.Error("Could not execute access logger template: %v", err.Error())
93+
}
94+
95+
lr.logger.Log(1, log.INFO, "%s", buf.String())
96+
}
97+
98+
func newAccessLogRecorder() *accessLogRecorder {
99+
return &accessLogRecorder{
100+
logger: log.GetLogger("access"),
101+
logTemplate: template.Must(template.New("log").Parse(setting.Log.AccessLogTemplate)),
102+
needRequestID: len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate),
103+
}
104+
}
105+
54106
// AccessLogger returns a middleware to log access logger
55107
func AccessLogger() func(http.Handler) http.Handler {
56-
logger := log.GetLogger("access")
57-
needRequestID := len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate)
58-
logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate)
108+
recorder := newAccessLogRecorder()
59109
return func(next http.Handler) http.Handler {
60110
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
61111
start := time.Now()
62-
63-
var requestID string
64-
if needRequestID {
65-
requestID = parseRequestIDFromRequestHeader(req)
66-
}
67-
68-
reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
69-
if err != nil {
70-
reqHost = req.RemoteAddr
71-
}
72-
73112
next.ServeHTTP(w, req)
74-
rw := w.(ResponseWriter)
75-
76-
identity := "-"
77-
data := middleware.GetContextData(req.Context())
78-
if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
79-
identity = signedUser.Name
80-
}
81-
buf := bytes.NewBuffer([]byte{})
82-
err = logTemplate.Execute(buf, routerLoggerOptions{
83-
req: req,
84-
Identity: &identity,
85-
Start: &start,
86-
ResponseWriter: rw,
87-
Ctx: map[string]any{
88-
"RemoteAddr": req.RemoteAddr,
89-
"RemoteHost": reqHost,
90-
"Req": req,
91-
},
92-
RequestID: &requestID,
93-
})
94-
if err != nil {
95-
log.Error("Could not execute access logger template: %v", err.Error())
96-
}
97-
98-
logger.Info("%s", buf.String())
113+
recorder.record(start, w.(ResponseWriter), req)
99114
})
100115
}
101116
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package context
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
"testing"
11+
"time"
12+
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/setting"
15+
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
type testAccessLoggerMock struct {
20+
logs []string
21+
}
22+
23+
func (t *testAccessLoggerMock) Log(skip int, level log.Level, format string, v ...any) {
24+
t.logs = append(t.logs, fmt.Sprintf(format, v...))
25+
}
26+
27+
func (t *testAccessLoggerMock) GetLevel() log.Level {
28+
return log.INFO
29+
}
30+
31+
type testAccessLoggerResponseWriterMock struct{}
32+
33+
func (t testAccessLoggerResponseWriterMock) Header() http.Header {
34+
return nil
35+
}
36+
37+
func (t testAccessLoggerResponseWriterMock) Before(f func(ResponseWriter)) {}
38+
39+
func (t testAccessLoggerResponseWriterMock) WriteHeader(statusCode int) {}
40+
41+
func (t testAccessLoggerResponseWriterMock) Write(bytes []byte) (int, error) {
42+
return 0, nil
43+
}
44+
45+
func (t testAccessLoggerResponseWriterMock) Flush() {}
46+
47+
func (t testAccessLoggerResponseWriterMock) WrittenStatus() int {
48+
return http.StatusOK
49+
}
50+
51+
func (t testAccessLoggerResponseWriterMock) Status() int {
52+
return t.WrittenStatus()
53+
}
54+
55+
func (t testAccessLoggerResponseWriterMock) WrittenSize() int {
56+
return 123123
57+
}
58+
59+
func TestAccessLogger(t *testing.T) {
60+
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}}"`
61+
recorder := newAccessLogRecorder()
62+
mockLogger := &testAccessLoggerMock{}
63+
recorder.logger = mockLogger
64+
req := &http.Request{
65+
RemoteAddr: "remote-addr",
66+
Method: "GET",
67+
Proto: "https",
68+
URL: &url.URL{Path: "/path"},
69+
}
70+
req.Header = http.Header{}
71+
req.Header.Add("Referer", "referer")
72+
req.Header.Add("User-Agent", "user-agent")
73+
recorder.record(time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), &testAccessLoggerResponseWriterMock{}, req)
74+
assert.Equal(t, []string{`remote-addr - - [02/Jan/2000:03:04:05 +0000] "GET /path https" 200 123123 "referer" "user-agent"`}, mockLogger.logs)
75+
}

services/context/response.go

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,26 @@ type ResponseWriter interface {
1515
http.Flusher
1616
web_types.ResponseStatusProvider
1717

18-
Before(func(ResponseWriter))
19-
20-
Status() int // used by access logger template
21-
Size() int // used by access logger template
18+
Before(fn func(ResponseWriter))
19+
Status() int
20+
WrittenSize() int
2221
}
2322

24-
var _ ResponseWriter = &Response{}
23+
var _ ResponseWriter = (*Response)(nil)
2524

2625
// Response represents a response
2726
type Response struct {
2827
http.ResponseWriter
2928
written int
3029
status int
31-
befores []func(ResponseWriter)
30+
beforeFuncs []func(ResponseWriter)
3231
beforeExecuted bool
3332
}
3433

3534
// Write writes bytes to HTTP endpoint
3635
func (r *Response) Write(bs []byte) (int, error) {
3736
if !r.beforeExecuted {
38-
for _, before := range r.befores {
37+
for _, before := range r.beforeFuncs {
3938
before(r)
4039
}
4140
r.beforeExecuted = true
@@ -51,18 +50,14 @@ func (r *Response) Write(bs []byte) (int, error) {
5150
return size, nil
5251
}
5352

54-
func (r *Response) Status() int {
55-
return r.status
56-
}
57-
58-
func (r *Response) Size() int {
53+
func (r *Response) WrittenSize() int {
5954
return r.written
6055
}
6156

6257
// WriteHeader write status code
6358
func (r *Response) WriteHeader(statusCode int) {
6459
if !r.beforeExecuted {
65-
for _, before := range r.befores {
60+
for _, before := range r.beforeFuncs {
6661
before(r)
6762
}
6863
r.beforeExecuted = true
@@ -80,24 +75,26 @@ func (r *Response) Flush() {
8075
}
8176
}
8277

78+
// Status returns status code written
79+
// TODO: use WrittenStatus instead
80+
func (r *Response) Status() int {
81+
return r.status
82+
}
83+
8384
// WrittenStatus returned status code written
8485
func (r *Response) WrittenStatus() int {
8586
return r.status
8687
}
8788

8889
// Before allows for a function to be called before the ResponseWriter has been written to. This is
8990
// useful for setting headers or any other operations that must happen before a response has been written.
90-
func (r *Response) Before(f func(ResponseWriter)) {
91-
r.befores = append(r.befores, f)
91+
func (r *Response) Before(fn func(ResponseWriter)) {
92+
r.beforeFuncs = append(r.beforeFuncs, fn)
9293
}
9394

9495
func WrapResponseWriter(resp http.ResponseWriter) *Response {
9596
if v, ok := resp.(*Response); ok {
9697
return v
9798
}
98-
return &Response{
99-
ResponseWriter: resp,
100-
status: 0,
101-
befores: make([]func(ResponseWriter), 0),
102-
}
99+
return &Response{ResponseWriter: resp}
103100
}

services/repository/branch.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,9 +668,12 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
668668
}
669669

670670
// BranchDivergingInfo contains the information about the divergence of a head branch to the base branch.
671-
// This struct is also used in templates, so it needs to search for all references before changing it.
672671
type BranchDivergingInfo struct {
672+
// whether the base branch contains new commits which are not in the head branch
673673
BaseHasNewCommits bool
674+
675+
// behind/after are number of commits that the head branch is behind/after the base branch, it's 0 if it's unable to calculate.
676+
// there could be a case that BaseHasNewCommits=true while the behind/after are both 0 (unable to calculate).
674677
HeadCommitsBehind int
675678
HeadCommitsAhead int
676679
}
@@ -681,11 +684,20 @@ func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repo
681684
if err != nil {
682685
return nil, err
683686
}
684-
687+
if headGitBranch.IsDeleted {
688+
return nil, git_model.ErrBranchNotExist{
689+
BranchName: headBranch,
690+
}
691+
}
685692
baseGitBranch, err := git_model.GetBranch(ctx, baseRepo.ID, baseBranch)
686693
if err != nil {
687694
return nil, err
688695
}
696+
if baseGitBranch.IsDeleted {
697+
return nil, git_model.ErrBranchNotExist{
698+
BranchName: baseBranch,
699+
}
700+
}
689701

690702
info := &BranchDivergingInfo{}
691703
if headGitBranch.CommitID == baseGitBranch.CommitID {
@@ -720,5 +732,6 @@ func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repo
720732
}
721733

722734
info.HeadCommitsBehind, info.HeadCommitsAhead = diff.Behind, diff.Ahead
735+
info.BaseHasNewCommits = info.HeadCommitsBehind > 0
723736
return info, nil
724737
}

0 commit comments

Comments
 (0)