Skip to content

Commit a02e12f

Browse files
authored
Merge branch 'release/v1.23' into backport-33298-v1.23
2 parents 107d891 + 7df1204 commit a02e12f

File tree

28 files changed

+212
-145
lines changed

28 files changed

+212
-145
lines changed

models/user/avatar.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,30 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
3838

3939
u.Avatar = avatars.HashEmail(seed)
4040

41-
// Don't share the images so that we can delete them easily
42-
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
43-
if err := png.Encode(w, img); err != nil {
44-
log.Error("Encode: %v", err)
41+
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
42+
if err != nil {
43+
// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
44+
// Don't share the images so that we can delete them easily
45+
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
46+
if err := png.Encode(w, img); err != nil {
47+
log.Error("Encode: %v", err)
48+
}
49+
return nil
50+
}); err != nil {
51+
return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
4552
}
46-
return err
47-
}); err != nil {
48-
return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
4953
}
5054

5155
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
5256
return err
5357
}
5458

55-
log.Info("New random avatar created: %d", u.ID)
5659
return nil
5760
}
5861

5962
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
6063
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
61-
if u.IsGhost() {
64+
if u.IsGhost() || u.IsGiteaActions() {
6265
return avatars.DefaultAvatarLink()
6366
}
6467

models/user/avatar_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
package user
55

66
import (
7+
"context"
8+
"io"
9+
"strings"
710
"testing"
811

912
"code.gitea.io/gitea/models/db"
13+
"code.gitea.io/gitea/models/unittest"
1014
"code.gitea.io/gitea/modules/setting"
15+
"code.gitea.io/gitea/modules/storage"
1116
"code.gitea.io/gitea/modules/test"
1217

1318
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
1420
)
1521

1622
func TestUserAvatarLink(t *testing.T) {
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
2632
link = u.AvatarLink(db.DefaultContext)
2733
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
2834
}
35+
36+
func TestUserAvatarGenerate(t *testing.T) {
37+
assert.NoError(t, unittest.PrepareTestDatabase())
38+
var err error
39+
tmpDir := t.TempDir()
40+
storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
41+
require.NoError(t, err)
42+
43+
u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
44+
45+
// there was no avatar, generate a new one
46+
assert.Empty(t, u.Avatar)
47+
err = GenerateRandomAvatar(db.DefaultContext, u)
48+
require.NoError(t, err)
49+
assert.NotEmpty(t, u.Avatar)
50+
51+
// make sure the generated one exists
52+
oldAvatarPath := u.CustomAvatarRelativePath()
53+
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
54+
require.NoError(t, err)
55+
// and try to change its content
56+
_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
57+
require.NoError(t, err)
58+
59+
// try to generate again
60+
err = GenerateRandomAvatar(db.DefaultContext, u)
61+
require.NoError(t, err)
62+
assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
63+
f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
64+
require.NoError(t, err)
65+
defer f.Close()
66+
content, _ := io.ReadAll(f)
67+
assert.Equal(t, "abcd", string(content))
68+
}

models/user/user_system.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ func NewGhostUser() *User {
2424
}
2525
}
2626

27+
func IsGhostUserName(name string) bool {
28+
return strings.EqualFold(name, GhostUserName)
29+
}
30+
2731
// IsGhost check if user is fake user for a deleted account
2832
func (u *User) IsGhost() bool {
2933
if u == nil {
@@ -48,6 +52,10 @@ const (
4852
ActionsEmail = "[email protected]"
4953
)
5054

55+
func IsGiteaActionsUserName(name string) bool {
56+
return strings.EqualFold(name, ActionsUserName)
57+
}
58+
5159
// NewActionsUser creates and returns a fake user for running the actions.
5260
func NewActionsUser() *User {
5361
return &User{
@@ -65,6 +73,16 @@ func NewActionsUser() *User {
6573
}
6674
}
6775

68-
func (u *User) IsActions() bool {
76+
func (u *User) IsGiteaActions() bool {
6977
return u != nil && u.ID == ActionsUserID
7078
}
79+
80+
func GetSystemUserByName(name string) *User {
81+
if IsGhostUserName(name) {
82+
return NewGhostUser()
83+
}
84+
if IsGiteaActionsUserName(name) {
85+
return NewActionsUser()
86+
}
87+
return nil
88+
}

models/user/user_system_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package user
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/db"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestSystemUser(t *testing.T) {
16+
u, err := GetPossibleUserByID(db.DefaultContext, -1)
17+
require.NoError(t, err)
18+
assert.Equal(t, "Ghost", u.Name)
19+
assert.Equal(t, "ghost", u.LowerName)
20+
assert.True(t, u.IsGhost())
21+
assert.True(t, IsGhostUserName("gHost"))
22+
23+
u, err = GetPossibleUserByID(db.DefaultContext, -2)
24+
require.NoError(t, err)
25+
assert.Equal(t, "gitea-actions", u.Name)
26+
assert.Equal(t, "gitea-actions", u.LowerName)
27+
assert.True(t, u.IsGiteaActions())
28+
assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
29+
30+
_, err = GetPossibleUserByID(db.DefaultContext, -3)
31+
require.Error(t, err)
32+
}

modules/httplib/request.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ func (r *Request) Param(key, value string) *Request {
9999
return r
100100
}
101101

102-
// Body adds request raw body.
103-
// it supports string and []byte.
102+
// Body adds request raw body. It supports string, []byte and io.Reader as body.
104103
func (r *Request) Body(data any) *Request {
105104
switch t := data.(type) {
105+
case nil: // do nothing
106106
case string:
107107
bf := bytes.NewBufferString(t)
108108
r.req.Body = io.NopCloser(bf)
@@ -111,6 +111,12 @@ func (r *Request) Body(data any) *Request {
111111
bf := bytes.NewBuffer(t)
112112
r.req.Body = io.NopCloser(bf)
113113
r.req.ContentLength = int64(len(t))
114+
case io.ReadCloser:
115+
r.req.Body = t
116+
case io.Reader:
117+
r.req.Body = io.NopCloser(t)
118+
default:
119+
panic(fmt.Sprintf("unsupported request body type %T", t))
114120
}
115121
return r
116122
}
@@ -141,7 +147,7 @@ func (r *Request) getResponse() (*http.Response, error) {
141147
}
142148
} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
143149
r.Header("Content-Type", "application/x-www-form-urlencoded")
144-
r.Body(paramBody)
150+
r.Body(paramBody) // string
145151
}
146152

147153
var err error
@@ -185,6 +191,7 @@ func (r *Request) getResponse() (*http.Response, error) {
185191
}
186192

187193
// Response executes request client gets response manually.
194+
// Caller MUST close the response body if no error occurs
188195
func (r *Request) Response() (*http.Response, error) {
189196
return r.getResponse()
190197
}

modules/lfs/http_client.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
7272

7373
url := fmt.Sprintf("%s/objects/batch", c.endpoint)
7474

75+
// Original: In some lfs server implementations, they require the ref attribute. #32838
7576
// `ref` is an "optional object describing the server ref that the objects belong to"
76-
// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
77+
// but some (incorrect) lfs servers like aliyun require it, so maybe adding an empty ref here doesn't break the correct ones.
7778
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
78-
request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
79+
//
80+
// UPDATE: it can't use "empty ref" here because it breaks others like https://github.com/go-gitea/gitea/issues/33453
81+
request := &BatchRequest{operation, c.transferNames(), nil, objects}
82+
7983
payload := new(bytes.Buffer)
8084
err := json.NewEncoder(payload).Encode(request)
8185
if err != nil {

modules/lfstransfer/backend/backend.go

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package backend
55

66
import (
7-
"bytes"
87
"context"
98
"encoding/base64"
109
"fmt"
@@ -29,7 +28,7 @@ var Capabilities = []string{
2928
"locking",
3029
}
3130

32-
var _ transfer.Backend = &GiteaBackend{}
31+
var _ transfer.Backend = (*GiteaBackend)(nil)
3332

3433
// GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
3534
type GiteaBackend struct {
@@ -78,17 +77,17 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
7877
headerAccept: mimeGitLFS,
7978
headerContentType: mimeGitLFS,
8079
}
81-
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
80+
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
8281
resp, err := req.Response()
8382
if err != nil {
8483
g.logger.Log("http request error", err)
8584
return nil, err
8685
}
86+
defer resp.Body.Close()
8787
if resp.StatusCode != http.StatusOK {
8888
g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
8989
return nil, statusCodeToErr(resp.StatusCode)
9090
}
91-
defer resp.Body.Close()
9291
respBytes, err := io.ReadAll(resp.Body)
9392
if err != nil {
9493
g.logger.Log("http read error", err)
@@ -158,8 +157,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
158157
return pointers, nil
159158
}
160159

161-
// Download implements transfer.Backend. The returned reader must be closed by the
162-
// caller.
160+
// Download implements transfer.Backend. The returned reader must be closed by the caller.
163161
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
164162
idMapStr, exists := args[argID]
165163
if !exists {
@@ -187,25 +185,25 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
187185
headerGiteaInternalAuth: g.internalAuth,
188186
headerAccept: mimeOctetStream,
189187
}
190-
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
188+
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
191189
resp, err := req.Response()
192190
if err != nil {
193-
return nil, 0, err
191+
return nil, 0, fmt.Errorf("failed to get response: %w", err)
194192
}
193+
// no need to close the body here by "defer resp.Body.Close()", see below
195194
if resp.StatusCode != http.StatusOK {
196195
return nil, 0, statusCodeToErr(resp.StatusCode)
197196
}
198-
defer resp.Body.Close()
199-
respBytes, err := io.ReadAll(resp.Body)
197+
198+
respSize, err := strconv.ParseInt(resp.Header.Get("X-Gitea-LFS-Content-Length"), 10, 64)
200199
if err != nil {
201-
return nil, 0, err
200+
return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
202201
}
203-
respSize := int64(len(respBytes))
204-
respBuf := io.NopCloser(bytes.NewBuffer(respBytes))
205-
return respBuf, respSize, nil
202+
// transfer.Backend will check io.Closer interface and close this Body reader
203+
return resp.Body, respSize, nil
206204
}
207205

208-
// StartUpload implements transfer.Backend.
206+
// Upload implements transfer.Backend.
209207
func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
210208
idMapStr, exists := args[argID]
211209
if !exists {
@@ -234,15 +232,14 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
234232
headerContentType: mimeOctetStream,
235233
headerContentLength: strconv.FormatInt(size, 10),
236234
}
237-
reqBytes, err := io.ReadAll(r)
238-
if err != nil {
239-
return err
240-
}
241-
req := newInternalRequest(g.ctx, url, http.MethodPut, headers, reqBytes)
235+
236+
req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
237+
req.Body(r)
242238
resp, err := req.Response()
243239
if err != nil {
244240
return err
245241
}
242+
defer resp.Body.Close()
246243
if resp.StatusCode != http.StatusOK {
247244
return statusCodeToErr(resp.StatusCode)
248245
}
@@ -284,11 +281,12 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
284281
headerAccept: mimeGitLFS,
285282
headerContentType: mimeGitLFS,
286283
}
287-
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
284+
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
288285
resp, err := req.Response()
289286
if err != nil {
290287
return transfer.NewStatus(transfer.StatusInternalServerError), err
291288
}
289+
defer resp.Body.Close()
292290
if resp.StatusCode != http.StatusOK {
293291
return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
294292
}

modules/lfstransfer/backend/lock.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
5050
headerAccept: mimeGitLFS,
5151
headerContentType: mimeGitLFS,
5252
}
53-
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
53+
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
5454
resp, err := req.Response()
5555
if err != nil {
5656
g.logger.Log("http request error", err)
@@ -102,7 +102,7 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
102102
headerAccept: mimeGitLFS,
103103
headerContentType: mimeGitLFS,
104104
}
105-
req := newInternalRequest(g.ctx, url, http.MethodPost, headers, bodyBytes)
105+
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
106106
resp, err := req.Response()
107107
if err != nil {
108108
g.logger.Log("http request error", err)
@@ -185,7 +185,7 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er
185185
headerAccept: mimeGitLFS,
186186
headerContentType: mimeGitLFS,
187187
}
188-
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
188+
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
189189
resp, err := req.Response()
190190
if err != nil {
191191
g.logger.Log("http request error", err)

0 commit comments

Comments
 (0)