···38383939 u.Avatar = avatars.HashEmail(seed)
40404141- // Don't share the images so that we can delete them easily
4242- if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
4343- if err := png.Encode(w, img); err != nil {
4444- log.Error("Encode: %v", err)
4141+ _, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
4242+ if err != nil {
4343+ // If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
4444+ // Don't share the images so that we can delete them easily
4545+ if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
4646+ if err := png.Encode(w, img); err != nil {
4747+ log.Error("Encode: %v", err)
4848+ }
4949+ return nil
5050+ }); err != nil {
5151+ return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
4552 }
4646- return err
4747- }); err != nil {
4848- return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
4953 }
50545155 if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
+68
models/user/avatar_test.go
···11+// Copyright 2024 The Gitea Authors. All rights reserved.
22+// SPDX-License-Identifier: MIT
33+44+package user
55+66+import (
77+ "context"
88+ "io"
99+ "strings"
1010+ "testing"
1111+1212+ "code.gitea.io/gitea/models/db"
1313+ "code.gitea.io/gitea/models/unittest"
1414+ "code.gitea.io/gitea/modules/setting"
1515+ "code.gitea.io/gitea/modules/storage"
1616+ "code.gitea.io/gitea/modules/test"
1717+1818+ "github.com/stretchr/testify/assert"
1919+ "github.com/stretchr/testify/require"
2020+)
2121+2222+func TestUserAvatarLink(t *testing.T) {
2323+ defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
2424+ defer test.MockVariableValue(&setting.AppSubURL, "")()
2525+2626+ u := &User{ID: 1, Avatar: "avatar.png"}
2727+ link := u.AvatarLink(db.DefaultContext)
2828+ assert.Equal(t, "https://localhost/avatars/avatar.png", link)
2929+3030+ setting.AppURL = "https://localhost/sub-path/"
3131+ setting.AppSubURL = "/sub-path"
3232+ link = u.AvatarLink(db.DefaultContext)
3333+ assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
3434+}
3535+3636+func TestUserAvatarGenerate(t *testing.T) {
3737+ require.NoError(t, unittest.PrepareTestDatabase())
3838+ var err error
3939+ tmpDir := t.TempDir()
4040+ storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
4141+ require.NoError(t, err)
4242+4343+ u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
4444+4545+ // there was no avatar, generate a new one
4646+ assert.Empty(t, u.Avatar)
4747+ err = GenerateRandomAvatar(db.DefaultContext, u)
4848+ require.NoError(t, err)
4949+ assert.NotEmpty(t, u.Avatar)
5050+5151+ // make sure the generated one exists
5252+ oldAvatarPath := u.CustomAvatarRelativePath()
5353+ _, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
5454+ require.NoError(t, err)
5555+ // and try to change its content
5656+ _, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
5757+ require.NoError(t, err)
5858+5959+ // try to generate again
6060+ err = GenerateRandomAvatar(db.DefaultContext, u)
6161+ require.NoError(t, err)
6262+ assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
6363+ f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
6464+ require.NoError(t, err)
6565+ defer f.Close()
6666+ content, _ := io.ReadAll(f)
6767+ assert.Equal(t, "abcd", string(content))
6868+}
+6-2
modules/lfs/http_client.go
···72727373 url := fmt.Sprintf("%s/objects/batch", c.endpoint)
74747575+ // 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"
7676- // but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
7777+ // 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
7878- request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
7979+ //
8080+ // UPDATE: it can't use "empty ref" here because it breaks others like https://github.com/go-gitea/gitea/issues/33453
8181+ request := &BatchRequest{operation, c.transferNames(), nil, objects}
8282+7983 payload := new(bytes.Buffer)
8084 err := json.NewEncoder(payload).Encode(request)
8185 if err != nil {
+9
modules/structs/org.go
···5757 Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
5858 RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"`
5959}
6060+6161+// RenameOrgOption options when renaming an organization
6262+type RenameOrgOption struct {
6363+ // New username for this org. This name cannot be in use yet by any other user.
6464+ //
6565+ // required: true
6666+ // unique: true
6767+ NewName string `json:"new_name" binding:"Required"`
6868+}
+1
release-notes/6763.md
···11+feat: [commit](https://codeberg.org/forgejo/forgejo/commit/689fb82a7043fdb2fee02195701b0bc728e99709) API endpoint to rename an organization