From 203f79a1210ce4fc2fbbbd543aaee9de16149a31 Mon Sep 17 00:00:00 2001
From: Kerwin Bryant <kerwin612@qq.com>
Date: Tue, 19 Nov 2024 14:57:55 +0800
Subject: [PATCH 1/9] Fix a compilation error in the Gitpod environment
 (#32559)

When opening the latest code in **Gitpod** and running `make
lint-backend`, the following error occurs:
```bash
gitpod /workspace/gitea (main) $ make lint-backend
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 run
# internal/profilerecord
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/goarch
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# unicode/utf8
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/coverage/rtcov
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/byteorder
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# cmp
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/itoa
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/race
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/goos
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/unsafeheader
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# unicode
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/godebugs
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/asan
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# math/bits
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/goexperiment
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/msan
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/runtime/atomic
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# sync/atomic
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/runtime/syscall
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# crypto/internal/alias
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# encoding
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# log/internal
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# vendor/golang.org/x/crypto/cryptobyte/asn1
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/golangci/golangci-lint/pkg/exitcodes
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/cpu
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# unicode/utf16
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# container/list
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# crypto/subtle
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/goversion
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# golang.org/x/exp/maps
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/ccojocar/zxcvbn-go/match
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# golang.org/x/exp/constraints
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# golang.org/x/tools/internal/packagesinternal
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/quasilyte/go-ruleguard/dsl/types
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# vendor/golang.org/x/crypto/internal/alias
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/nettrace
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/google/go-cmp/cmp/internal/flags
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/gobwas/glob/util/runes
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# internal/platform
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# crypto/internal/boring/sig
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/quasilyte/gogrep/internal/stdinfo
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/daixiang0/gci/pkg/utils
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/quasilyte/stdinfo
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/Antonboom/testifylint/internal/testify
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# hash/maphash
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# github.com/nunnatsa/ginkgolinter/version
compile: version "go1.23.1" does not match go tool version "go1.22.9"
# google.golang.org/protobuf/internal/flags
compile: version "go1.23.1" does not match go tool version "go1.22.9"
make: *** [Makefile:413: lint-go] Error 1
```

(cherry picked from commit 32456b6f314f993efdc65fc90248b6fd1a8d55ef)
---
 flake.nix | 1 -
 1 file changed, 1 deletion(-)

diff --git a/flake.nix b/flake.nix
index e2f273e341..880b1279b6 100644
--- a/flake.nix
+++ b/flake.nix
@@ -29,7 +29,6 @@
             poetry
 
             # backend
-            go_1_22
             gofumpt
             sqlite
           ];

From f3afd18b76c50e8493b2163db2711214e14d4cfd Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Tue, 19 Nov 2024 08:21:13 -0800
Subject: [PATCH 2/9] Remove unnecessary code (#32560)

PushMirrors only be used in the repository setting page. So it should
not be loaded on every repository page.

(cherry picked from commit 0d5abd9b3e04a09f5d7de720c99e3451723e028e)
---
 services/context/repo.go | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/services/context/repo.go b/services/context/repo.go
index d2cee086d6..45a046eff6 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -413,14 +413,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
 		}
 	}
 
-	pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
-	if err != nil {
-		ctx.ServerError("GetPushMirrorsByRepoID", err)
-		return
-	}
-
 	ctx.Repo.Repository = repo
-	ctx.Data["PushMirrors"] = pushMirrors
 	ctx.Data["RepoName"] = ctx.Repo.Repository.Name
 	ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
 	ctx.Data["DefaultWikiBranchName"] = setting.Repository.DefaultBranch

From e9928b757796b69e099f4c15d6efdd99bba26f25 Mon Sep 17 00:00:00 2001
From: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:05:06 -0800
Subject: [PATCH 3/9] Remove duplicate empty repo check in delete branch API
 (#32569)

Found while working on #32433.

This branch will never be executed because we have would have already
made the same check a couple lines above.

(cherry picked from commit 355889dbc2432554f0bcdb22f918488849f0016c)
---
 routers/api/v1/repo/branch.go | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index a468fd90d0..e3e6efa781 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -133,11 +133,6 @@ func DeleteBranch(ctx *context.APIContext) {
 
 	branchName := ctx.Params("*")
 
-	if ctx.Repo.Repository.IsEmpty {
-		ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
-		return
-	}
-
 	// check whether branches of this repository has been synced
 	totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
 		RepoID:          ctx.Repo.Repository.ID,

From 262c48409b1224e3f6dc63c8d1e04fef0e0cf2c0 Mon Sep 17 00:00:00 2001
From: Marcell Mars <ki.ber@kom.uni.st>
Date: Wed, 20 Nov 2024 15:22:48 +0100
Subject: [PATCH 4/9] Support HTTP POST requests to `/userinfo`, aligning to
 OpenID Core specification (#32578)

This PR adds support for the HTTP POST requests to `/userinfo` endpoint.
While the OpenID Core specification says both are supported and
recommends using HTTP GET.

ref: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
(cherry picked from commit 56bff7ae234ee21d0e4524e401a49385c383ccaf)

Conflicts:
	routers/web/web.go
  trivial context conflict
---
 routers/web/web.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/routers/web/web.go b/routers/web/web.go
index 1a764103fc..fdfda68b1e 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -530,7 +530,7 @@ func registerRoutes(m *web.Route) {
 			m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
 		}, ignSignInAndCsrf, reqSignIn)
 
-		m.Methods("GET, OPTIONS", "/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth)
+		m.Methods("GET, POST, OPTIONS", "/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth)
 		m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
 		m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys)
 		m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)

From ddabba5f89c4b196daeeb2af17de9ec2cec14b63 Mon Sep 17 00:00:00 2001
From: Rowan Bohde <rowan.bohde@gmail.com>
Date: Wed, 20 Nov 2024 09:24:09 -0600
Subject: [PATCH 5/9] allow the actions user to login via the jwt token
 (#32527)

We have some actions that leverage the Gitea API that began receiving
401 errors, with a message that the user was not found. These actions
use the `ACTIONS_RUNTIME_TOKEN` env var in the actions job to
authenticate with the Gitea API. The format of this env var in actions
jobs changed with go-gitea/gitea/pull/28885 to be a JWT (with a
corresponding update to `act_runner`) Since it was a JWT, the OAuth
parsing logic attempted to parse it as an OAuth token, and would return
user not found, instead of falling back to look up the running task and
assigning it to the actions user.

Make ACTIONS_RUNTIME_TOKEN in action runners could be used,
attempting to parse Oauth JWTs. The code to parse potential old
`ACTION_RUNTIME_TOKEN` was kept in case someone is running an older
version of act_runner that doesn't support the Actions JWT.

(cherry picked from commit 407b6e6dfc7ee9ebb8a16c7f1a786e4c24d0516e)

Conflicts:
	services/auth/oauth2.go
  trivial context conflicts because OAuth2 scopes are in Forgejo and
  not yet in Gitea
---
 models/fixtures/action_task.yml | 19 ++++++++++++
 services/actions/auth.go        | 11 +++++--
 services/auth/oauth2.go         | 24 +++++++++++++-
 services/auth/oauth2_test.go    | 55 +++++++++++++++++++++++++++++++++
 4 files changed, 105 insertions(+), 4 deletions(-)
 create mode 100644 services/auth/oauth2_test.go

diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml
index 443effe08c..d88a8ed8a9 100644
--- a/models/fixtures/action_task.yml
+++ b/models/fixtures/action_task.yml
@@ -1,3 +1,22 @@
+-
+  id: 46
+  attempt: 3
+  runner_id: 1
+  status: 3 # 3 is the status code for "cancelled"
+  started: 1683636528
+  stopped: 1683636626
+  repo_id: 4
+  owner_id: 1
+  commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+  is_fork_pull_request: 0
+  token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa
+  token_salt: eeeeeeee
+  token_last_eight: eeeeeeee
+  log_filename: artifact-test2/2f/47.log
+  log_in_storage: 1
+  log_length: 707
+  log_size: 90179
+  log_expired: 0
 -
   id: 47
   job_id: 192
diff --git a/services/actions/auth.go b/services/actions/auth.go
index 8e934d89a8..1ef21f6e0e 100644
--- a/services/actions/auth.go
+++ b/services/actions/auth.go
@@ -83,7 +83,12 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
 		return 0, fmt.Errorf("split token failed")
 	}
 
-	token, err := jwt.ParseWithClaims(parts[1], &actionsClaims{}, func(t *jwt.Token) (any, error) {
+	return TokenToTaskID(parts[1])
+}
+
+// TokenToTaskID returns the TaskID associated with the provided JWT token
+func TokenToTaskID(token string) (int64, error) {
+	parsedToken, err := jwt.ParseWithClaims(token, &actionsClaims{}, func(t *jwt.Token) (any, error) {
 		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
 			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
 		}
@@ -93,8 +98,8 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) {
 		return 0, err
 	}
 
-	c, ok := token.Claims.(*actionsClaims)
-	if !token.Valid || !ok {
+	c, ok := parsedToken.Claims.(*actionsClaims)
+	if !parsedToken.Valid || !ok {
 		return 0, fmt.Errorf("invalid token claim")
 	}
 
diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go
index 8b625a193e..b983e57ecd 100644
--- a/services/auth/oauth2.go
+++ b/services/auth/oauth2.go
@@ -18,6 +18,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/web/middleware"
+	"code.gitea.io/gitea/services/actions"
 	"code.gitea.io/gitea/services/auth/source/oauth2"
 )
 
@@ -94,6 +95,18 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) (int64, stri
 	return grant.UserID, grantScopes
 }
 
+// CheckTaskIsRunning verifies that the TaskID corresponds to a running task
+func CheckTaskIsRunning(ctx context.Context, taskID int64) bool {
+	// Verify the task exists
+	task, err := actions_model.GetTaskByID(ctx, taskID)
+	if err != nil {
+		return false
+	}
+
+	// Verify that it's running
+	return task.Status == actions_model.StatusRunning
+}
+
 // OAuth2 implements the Auth interface and authenticates requests
 // (API requests only) by looking for an OAuth token in query parameters or the
 // "Authorization" header.
@@ -137,8 +150,17 @@ func parseToken(req *http.Request) (string, bool) {
 func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
 	// Let's see if token is valid.
 	if strings.Contains(tokenSHA, ".") {
-		uid, grantScopes := CheckOAuthAccessToken(ctx, tokenSHA)
+		// First attempt to decode an actions JWT, returning the actions user
+		if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
+			if CheckTaskIsRunning(ctx, taskID) {
+				store.GetData()["IsActionsToken"] = true
+				store.GetData()["ActionsTaskID"] = taskID
+				return user_model.ActionsUserID
+			}
+		}
 
+		// Otherwise, check if this is an OAuth access token
+		uid, grantScopes := CheckOAuthAccessToken(ctx, tokenSHA)
 		if uid != 0 {
 			store.GetData()["IsApiToken"] = true
 			if grantScopes != "" {
diff --git a/services/auth/oauth2_test.go b/services/auth/oauth2_test.go
new file mode 100644
index 0000000000..c9b4ed06cc
--- /dev/null
+++ b/services/auth/oauth2_test.go
@@ -0,0 +1,55 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package auth
+
+import (
+	"context"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/web/middleware"
+	"code.gitea.io/gitea/services/actions"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestUserIDFromToken(t *testing.T) {
+	require.NoError(t, unittest.PrepareTestDatabase())
+
+	t.Run("Actions JWT", func(t *testing.T) {
+		const RunningTaskID = 47
+		token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
+		require.NoError(t, err)
+
+		ds := make(middleware.ContextData)
+
+		o := OAuth2{}
+		uid := o.userIDFromToken(context.Background(), token, ds)
+		assert.Equal(t, int64(user_model.ActionsUserID), uid)
+		assert.Equal(t, true, ds["IsActionsToken"])
+		assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID))
+	})
+}
+
+func TestCheckTaskIsRunning(t *testing.T) {
+	require.NoError(t, unittest.PrepareTestDatabase())
+	cases := map[string]struct {
+		TaskID   int64
+		Expected bool
+	}{
+		"Running":   {TaskID: 47, Expected: true},
+		"Missing":   {TaskID: 1, Expected: false},
+		"Cancelled": {TaskID: 46, Expected: false},
+	}
+
+	for name := range cases {
+		c := cases[name]
+		t.Run(name, func(t *testing.T) {
+			actual := CheckTaskIsRunning(context.Background(), c.TaskID)
+			assert.Equal(t, c.Expected, actual)
+		})
+	}
+}

From 64824290912b6300ede2b2f95ff77d55dde9859b Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Tue, 19 Nov 2024 11:44:21 -0800
Subject: [PATCH 6/9] Fix submodule parsing

(cherry picked from commit 33850a83fe4ebd23a762a7aac81614c42e303bfa)

This really is just the cherry pick of 407b6e6dfc7ee9ebb8a16c7f1a786e4c24d0516e
which is the first commit of the pull request, the one with the
change. The rest of the changes is a refactor that is unrelated to the
bug fix.

Conflicts:
	modules/git/commit_test.go
  trivial context conflict
---
 modules/git/commit.go      | 45 ++++++++++++++++++--------------------
 modules/git/commit_test.go | 30 +++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 24 deletions(-)

diff --git a/modules/git/commit.go b/modules/git/commit.go
index b5ae2e0e52..0f3b749227 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -17,6 +17,8 @@ import (
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/util"
+
+	"github.com/go-git/go-git/v5/config"
 )
 
 // Commit represents a git commit.
@@ -365,37 +367,32 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) {
 		return nil, err
 	}
 
-	rd, err := entry.Blob().DataAsync()
+	content, err := entry.Blob().GetBlobContent(10 * 1024)
 	if err != nil {
 		return nil, err
 	}
 
-	defer rd.Close()
-	scanner := bufio.NewScanner(rd)
-	c.submoduleCache = newObjectCache()
-	var ismodule bool
-	var path string
-	for scanner.Scan() {
-		if strings.HasPrefix(scanner.Text(), "[submodule") {
-			ismodule = true
-			continue
-		}
-		if ismodule {
-			fields := strings.Split(scanner.Text(), "=")
-			k := strings.TrimSpace(fields[0])
-			if k == "path" {
-				path = strings.TrimSpace(fields[1])
-			} else if k == "url" {
-				c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
-				ismodule = false
-			}
-		}
+	c.submoduleCache, err = parseSubmoduleContent([]byte(content))
+	if err != nil {
+		return nil, err
 	}
-	if err = scanner.Err(); err != nil {
-		return nil, fmt.Errorf("GetSubModules scan: %w", err)
+	return c.submoduleCache, nil
+}
+
+func parseSubmoduleContent(bs []byte) (*ObjectCache, error) {
+	cfg := config.NewModules()
+	if err := cfg.Unmarshal(bs); err != nil {
+		return nil, err
+	}
+	submoduleCache := newObjectCache()
+	if len(cfg.Submodules) == 0 {
+		return nil, fmt.Errorf("no submodules found")
+	}
+	for _, subModule := range cfg.Submodules {
+		submoduleCache.Set(subModule.Path, subModule.URL)
 	}
 
-	return c.submoduleCache, nil
+	return submoduleCache, nil
 }
 
 // GetSubModule get the sub module according entryname
diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go
index af85bfe093..6bb7d776f5 100644
--- a/modules/git/commit_test.go
+++ b/modules/git/commit_test.go
@@ -369,3 +369,33 @@ func TestParseCommitRenames(t *testing.T) {
 		assert.Equal(t, testcase.renames, renames)
 	}
 }
+
+func Test_parseSubmoduleContent(t *testing.T) {
+	submoduleFiles := []struct {
+		fileContent  string
+		expectedPath string
+		expectedURL  string
+	}{
+		{
+			fileContent: `[submodule "jakarta-servlet"]
+url = ../../ALP-pool/jakarta-servlet
+path = jakarta-servlet`,
+			expectedPath: "jakarta-servlet",
+			expectedURL:  "../../ALP-pool/jakarta-servlet",
+		},
+		{
+			fileContent: `[submodule "jakarta-servlet"]
+path = jakarta-servlet
+url = ../../ALP-pool/jakarta-servlet`,
+			expectedPath: "jakarta-servlet",
+			expectedURL:  "../../ALP-pool/jakarta-servlet",
+		},
+	}
+	for _, kase := range submoduleFiles {
+		submodule, err := parseSubmoduleContent([]byte(kase.fileContent))
+		require.NoError(t, err)
+		v, ok := submodule.Get(kase.expectedPath)
+		assert.True(t, ok)
+		assert.Equal(t, kase.expectedURL, v)
+	}
+}

From 32a91add34519ef7768ec907888ed837ad0dde2f Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 20 Nov 2024 20:55:32 -0800
Subject: [PATCH 7/9] Fix GetInactiveUsers (#32540)

Fix #31480

(cherry picked from commit 9bf821ae6c108379d22ae11d8d5784a4ed7ad647)

Conflicts:
	models/user/user_test.go
  trivial context conflict
---
 models/fixtures/user.yml |  1 +
 models/user/user.go      | 18 ++++++++++++------
 models/user/user_test.go | 14 ++++++++++++++
 3 files changed, 27 insertions(+), 6 deletions(-)

diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 8e216fbc7d..73b9a97e1b 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -332,6 +332,7 @@
   repo_admin_change_team_access: false
   theme: ""
   keep_activity_private: false
+  created_unix: 1730468968
 
 -
   id: 10
diff --git a/models/user/user.go b/models/user/user.go
index 0fa8bb0ca9..96f8c3f729 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -50,19 +50,19 @@ const (
 	UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
 
 	// UserTypeOrganization defines an organization
-	UserTypeOrganization
+	UserTypeOrganization // 1
 
 	// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
-	UserTypeUserReserved
+	UserTypeUserReserved // 2
 
 	// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
-	UserTypeOrganizationReserved
+	UserTypeOrganizationReserved // 3
 
 	// UserTypeBot defines a bot user
-	UserTypeBot
+	UserTypeBot // 4
 
 	// UserTypeRemoteUser defines a remote user for federated users
-	UserTypeRemoteUser
+	UserTypeRemoteUser // 5
 )
 
 const (
@@ -919,7 +919,13 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
 
 // GetInactiveUsers gets all inactive users
 func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
-	var cond builder.Cond = builder.Eq{"is_active": false}
+	cond := builder.And(
+		builder.Eq{"is_active": false},
+		builder.Or( // only plain user
+			builder.Eq{"`type`": UserTypeIndividual},
+			builder.Eq{"`type`": UserTypeUserReserved},
+		),
+	)
 
 	if olderThan > 0 {
 		cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 6f20a577f3..ee456e3ce4 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -766,3 +766,17 @@ func TestVerifyUserAuthorizationToken(t *testing.T) {
 		assert.Nil(t, authToken)
 	})
 }
+
+func TestGetInactiveUsers(t *testing.T) {
+	require.NoError(t, unittest.PrepareTestDatabase())
+
+	// all inactive users
+	// user1's createdunix is 1730468968
+	users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
+	require.NoError(t, err)
+	assert.Len(t, users, 1)
+	interval := time.Now().Unix() - 1730468968 + 3600*24
+	users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
+	require.NoError(t, err)
+	require.Empty(t, users)
+}

From eb0645f1854b7903b8d771480fffd6a4958a2988 Mon Sep 17 00:00:00 2001
From: Rowan Bohde <rowan.bohde@gmail.com>
Date: Wed, 20 Nov 2024 22:30:48 -0600
Subject: [PATCH 8/9] disable gravatar in test (#32529)

When running e2e tests on flaky networks, gravatar can cause a timeout
and test failures. Turn off, and populate avatars on e2e test suite run
to make them reliable.

(cherry picked from commit 9ac74a1a408136455a9e0586fb8e65163048597b)

Conflicts:
	models/fixtures/user.yml
	services/repository/contributors_graph_test.go
  trivial context conflicts
---
 models/fixtures/system_setting.yml            |   2 +-
 models/fixtures/user.yml                      | 161 +++++++++---------
 modules/repository/commits_test.go            |   9 +-
 .../repository/contributors_graph_test.go     |   4 +-
 tests/integration/opengraph_test.go           |  21 ++-
 5 files changed, 99 insertions(+), 98 deletions(-)

diff --git a/models/fixtures/system_setting.yml b/models/fixtures/system_setting.yml
index 30542bc82a..dcad176c89 100644
--- a/models/fixtures/system_setting.yml
+++ b/models/fixtures/system_setting.yml
@@ -1,7 +1,7 @@
 -
   id: 1
   setting_key: 'picture.disable_gravatar'
-  setting_value: 'false'
+  setting_value: 'true'
   version: 1
   created: 1653533198
   updated: 1653533198
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 73b9a97e1b..dbc6fc3e38 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -23,9 +23,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar1
+  avatar: ""
   avatar_email: user1@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -60,8 +60,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar2
+  avatar: ""
   avatar_email: user2@example.com
+  # cause a random avatar to be generated when referenced for test purposes
   use_custom_avatar: false
   num_followers: 2
   num_following: 1
@@ -97,9 +98,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar3
+  avatar: ""
   avatar_email: org3@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -134,9 +135,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar4
+  avatar: ""
   avatar_email: user4@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 1
   num_stars: 0
@@ -171,9 +172,9 @@
   allow_import_local: false
   allow_create_organization: false
   prohibit_login: false
-  avatar: avatar5
+  avatar: ""
   avatar_email: user5@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -208,9 +209,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar6
+  avatar: ""
   avatar_email: org6@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -245,9 +246,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar7
+  avatar: ""
   avatar_email: org7@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -282,9 +283,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar8
+  avatar: ""
   avatar_email: user8@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 1
   num_following: 1
   num_stars: 0
@@ -319,9 +320,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar9
+  avatar: ""
   avatar_email: user9@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -357,9 +358,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar10
+  avatar: ""
   avatar_email: user10@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -394,9 +395,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar11
+  avatar: ""
   avatar_email: user11@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -431,9 +432,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar12
+  avatar: ""
   avatar_email: user12@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -468,9 +469,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar13
+  avatar: ""
   avatar_email: user13@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -505,9 +506,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar14
+  avatar: ""
   avatar_email: user13@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -542,9 +543,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar15
+  avatar: ""
   avatar_email: user15@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -579,9 +580,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar16
+  avatar: ""
   avatar_email: user16@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -616,9 +617,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar17
+  avatar: ""
   avatar_email: org17@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -653,9 +654,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar18
+  avatar: ""
   avatar_email: user18@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -690,9 +691,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar19
+  avatar: ""
   avatar_email: org19@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -727,9 +728,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar20
+  avatar: ""
   avatar_email: user20@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -764,9 +765,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar21
+  avatar: ""
   avatar_email: user21@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -801,9 +802,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar22
+  avatar: ""
   avatar_email: limited_org@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -838,9 +839,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar23
+  avatar: ""
   avatar_email: privated_org@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -875,9 +876,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar24
+  avatar: ""
   avatar_email: user24@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -912,9 +913,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar25
+  avatar: ""
   avatar_email: org25@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -949,9 +950,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar26
+  avatar: ""
   avatar_email: org26@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -986,9 +987,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar27
+  avatar: ""
   avatar_email: user27@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1023,9 +1024,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar28
+  avatar: ""
   avatar_email: user28@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1060,9 +1061,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar29
+  avatar: ""
   avatar_email: user29@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1097,9 +1098,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar29
+  avatar: ""
   avatar_email: user30@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1134,9 +1135,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar31
+  avatar: ""
   avatar_email: user31@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 1
   num_stars: 0
@@ -1171,9 +1172,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar32
+  avatar: ""
   avatar_email: user30@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1208,9 +1209,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar33
+  avatar: ""
   avatar_email: user33@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 1
   num_following: 0
   num_stars: 0
@@ -1246,7 +1247,7 @@
   allow_import_local: false
   allow_create_organization: false
   prohibit_login: false
-  avatar: avatar34
+  avatar: ""
   avatar_email: user34@example.com
   use_custom_avatar: true
   num_followers: 0
@@ -1283,9 +1284,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar35
+  avatar: ""
   avatar_email: private_org35@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1320,9 +1321,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar22
+  avatar: ""
   avatar_email: abcde@gitea.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1357,9 +1358,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: true
-  avatar: avatar29
+  avatar: ""
   avatar_email: user37@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1394,9 +1395,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar38
+  avatar: ""
   avatar_email: user38@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1431,9 +1432,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar39
+  avatar: ""
   avatar_email: user39@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1468,9 +1469,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar40
+  avatar: ""
   avatar_email: user40@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
@@ -1505,9 +1506,9 @@
   allow_import_local: false
   allow_create_organization: true
   prohibit_login: false
-  avatar: avatar41
+  avatar: ""
   avatar_email: org41@example.com
-  use_custom_avatar: false
+  use_custom_avatar: true
   num_followers: 0
   num_following: 0
   num_stars: 0
diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go
index 82841b3268..a22f3d07b8 100644
--- a/modules/repository/commits_test.go
+++ b/modules/repository/commits_test.go
@@ -4,8 +4,6 @@
 package repository
 
 import (
-	"crypto/md5"
-	"fmt"
 	"strconv"
 	"testing"
 	"time"
@@ -126,15 +124,12 @@ func TestPushCommits_AvatarLink(t *testing.T) {
 		},
 	}
 
-	setting.GravatarSource = "https://secure.gravatar.com/avatar"
-	setting.OfflineMode = true
-
 	assert.Equal(t,
-		"/avatars/avatar2?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor),
+		"/avatars/ab53a2911ddf9b4817ac01ddcd3d975f?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor),
 		pushCommits.AvatarLink(db.DefaultContext, "user2@example.com"))
 
 	assert.Equal(t,
-		fmt.Sprintf("https://secure.gravatar.com/avatar/%x?d=identicon&s=%d", md5.Sum([]byte("nonexistent@example.com")), 28*setting.Avatar.RenderedSizeFactor),
+		"/assets/img/avatar_default.png",
 		pushCommits.AvatarLink(db.DefaultContext, "nonexistent@example.com"))
 }
 
diff --git a/services/repository/contributors_graph_test.go b/services/repository/contributors_graph_test.go
index 3f8d614501..c62bef25a1 100644
--- a/services/repository/contributors_graph_test.go
+++ b/services/repository/contributors_graph_test.go
@@ -43,7 +43,7 @@ func TestRepository_ContributorsGraph(t *testing.T) {
 	dataString, isData := mockCache.Get("key2").(string)
 	assert.True(t, isData)
 	// Verify that JSON is actually stored in the cache.
-	assert.JSONEq(t, `{"ethantkoenig@gmail.com":{"name":"Ethan Koenig","login":"","avatar_link":"https://secure.gravatar.com/avatar/b42fb195faa8c61b8d88abfefe30e9e3?d=identicon","home_link":"","total_commits":1,"weeks":{"1511654400000":{"week":1511654400000,"additions":3,"deletions":0,"commits":1}}},"jimmy.praet@telenet.be":{"name":"Jimmy Praet","login":"","avatar_link":"https://secure.gravatar.com/avatar/93c49b7c89eb156971d11161c9b52795?d=identicon","home_link":"","total_commits":1,"weeks":{"1624752000000":{"week":1624752000000,"additions":2,"deletions":0,"commits":1}}},"jon@allspice.io":{"name":"Jon","login":"","avatar_link":"https://secure.gravatar.com/avatar/00388ce725e6886f3e07c3733007289b?d=identicon","home_link":"","total_commits":1,"weeks":{"1607817600000":{"week":1607817600000,"additions":10,"deletions":0,"commits":1}}},"total":{"name":"Total","login":"","avatar_link":"","home_link":"","total_commits":3,"weeks":{"1511654400000":{"week":1511654400000,"additions":3,"deletions":0,"commits":1},"1607817600000":{"week":1607817600000,"additions":10,"deletions":0,"commits":1},"1624752000000":{"week":1624752000000,"additions":2,"deletions":0,"commits":1}}}}`, dataString)
+	assert.JSONEq(t, `{"ethantkoenig@gmail.com":{"name":"Ethan Koenig","login":"","avatar_link":"/assets/img/avatar_default.png","home_link":"","total_commits":1,"weeks":{"1511654400000":{"week":1511654400000,"additions":3,"deletions":0,"commits":1}}},"jimmy.praet@telenet.be":{"name":"Jimmy Praet","login":"","avatar_link":"/assets/img/avatar_default.png","home_link":"","total_commits":1,"weeks":{"1624752000000":{"week":1624752000000,"additions":2,"deletions":0,"commits":1}}},"jon@allspice.io":{"name":"Jon","login":"","avatar_link":"/assets/img/avatar_default.png","home_link":"","total_commits":1,"weeks":{"1607817600000":{"week":1607817600000,"additions":10,"deletions":0,"commits":1}}},"total":{"name":"Total","login":"","avatar_link":"","home_link":"","total_commits":3,"weeks":{"1511654400000":{"week":1511654400000,"additions":3,"deletions":0,"commits":1},"1607817600000":{"week":1607817600000,"additions":10,"deletions":0,"commits":1},"1624752000000":{"week":1624752000000,"additions":2,"deletions":0,"commits":1}}}}`, dataString)
 
 	var data map[string]*ContributorData
 	require.NoError(t, json.Unmarshal([]byte(dataString), &data))
@@ -62,7 +62,7 @@ func TestRepository_ContributorsGraph(t *testing.T) {
 
 	assert.EqualValues(t, &ContributorData{
 		Name:         "Ethan Koenig",
-		AvatarLink:   "https://secure.gravatar.com/avatar/b42fb195faa8c61b8d88abfefe30e9e3?d=identicon",
+		AvatarLink:   "/assets/img/avatar_default.png",
 		TotalCommits: 1,
 		Weeks: map[int64]*WeekData{
 			1511654400000: {
diff --git a/tests/integration/opengraph_test.go b/tests/integration/opengraph_test.go
index 8d29e4548d..69eef7f0d9 100644
--- a/tests/integration/opengraph_test.go
+++ b/tests/integration/opengraph_test.go
@@ -5,6 +5,7 @@ package integration
 
 import (
 	"net/http"
+	"strings"
 	"testing"
 
 	"code.gitea.io/gitea/modules/setting"
@@ -42,7 +43,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:title":     "User Thirty",
 				"og:url":       setting.AppURL + "user30",
 				"og:type":      "profile",
-				"og:image":     "https://secure.gravatar.com/avatar/eae1f44b34ff27284cb0792c7601c89c?d=identicon",
+				"og:image":     "http://localhost:3003/assets/img/avatar_default.png",
 				"og:site_name": siteName,
 			},
 		},
@@ -54,7 +55,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:url":         setting.AppURL + "the_34-user.with.all.allowedChars",
 				"og:description": "some [commonmark](https://commonmark.org/)!",
 				"og:type":        "profile",
-				"og:image":       setting.AppURL + "avatars/avatar34",
+				"og:image":       "http://localhost:3003/assets/img/avatar_default.png",
 				"og:site_name":   siteName,
 			},
 		},
@@ -66,7 +67,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:url":         setting.AppURL + "user2/repo1/issues/1",
 				"og:description": "content for the first issue",
 				"og:type":        "object",
-				"og:image":       "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon",
+				"og:image":       "http://localhost:3003/avatars/ab53a2911ddf9b4817ac01ddcd3d975f",
 				"og:site_name":   siteName,
 			},
 		},
@@ -78,7 +79,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:url":         setting.AppURL + "user2/repo1/pulls/2",
 				"og:description": "content for the second issue",
 				"og:type":        "object",
-				"og:image":       "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon",
+				"og:image":       "http://localhost:3003/avatars/ab53a2911ddf9b4817ac01ddcd3d975f",
 				"og:site_name":   siteName,
 			},
 		},
@@ -89,7 +90,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:title":     "repo49/test/test.txt at master",
 				"og:url":       setting.AppURL + "/user27/repo49/src/branch/master/test/test.txt",
 				"og:type":      "object",
-				"og:image":     "https://secure.gravatar.com/avatar/7095710e927665f1bdd1ced94152f232?d=identicon",
+				"og:image":     "http://localhost:3003/assets/img/avatar_default.png",
 				"og:site_name": siteName,
 			},
 		},
@@ -100,7 +101,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:title":     "Page With Spaced Name",
 				"og:url":       setting.AppURL + "/user2/repo1/wiki/Page-With-Spaced-Name",
 				"og:type":      "object",
-				"og:image":     "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon",
+				"og:image":     "http://localhost:3003/avatars/ab53a2911ddf9b4817ac01ddcd3d975f",
 				"og:site_name": siteName,
 			},
 		},
@@ -111,7 +112,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:title":     "repo1",
 				"og:url":       setting.AppURL + "user2/repo1",
 				"og:type":      "object",
-				"og:image":     "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon",
+				"og:image":     "http://localhost:3003/avatars/ab53a2911ddf9b4817ac01ddcd3d975f",
 				"og:site_name": siteName,
 			},
 		},
@@ -123,7 +124,7 @@ func TestOpenGraphProperties(t *testing.T) {
 				"og:url":         setting.AppURL + "user27/repo49",
 				"og:description": "A wonderful repository with more than just a README.md",
 				"og:type":        "object",
-				"og:image":       "https://secure.gravatar.com/avatar/7095710e927665f1bdd1ced94152f232?d=identicon",
+				"og:image":       "http://localhost:3003/assets/img/avatar_default.png",
 				"og:site_name":   siteName,
 			},
 		},
@@ -141,6 +142,10 @@ func TestOpenGraphProperties(t *testing.T) {
 				assert.True(t, foundProp)
 				content, foundContent := selection.Attr("content")
 				assert.True(t, foundContent, "opengraph meta tag without a content property")
+				if prop == "og:image" {
+					content = strings.ReplaceAll(content, "http://localhost:3001", "http://localhost:3003")
+					content = strings.ReplaceAll(content, "http://localhost:3002", "http://localhost:3003")
+				}
 				foundProps[prop] = content
 			})
 

From d3ebc5b161599f31df45ae551b5b2a4e76ec19b8 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Sun, 24 Nov 2024 15:43:19 +0000
Subject: [PATCH 9/9] chore(release-notes): notes for the week 2024-48 weekly
 cherry pick

---
 release-notes/6062.md | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 release-notes/6062.md

diff --git a/release-notes/6062.md b/release-notes/6062.md
new file mode 100644
index 0000000000..7ae7f87548
--- /dev/null
+++ b/release-notes/6062.md
@@ -0,0 +1,4 @@
+fix: [commit](https://codeberg.org/forgejo/forgejo/commit/32a91add34519ef7768ec907888ed837ad0dde2f) Fix GetInactiveUsers
+fix: [commit](https://codeberg.org/forgejo/forgejo/commit/64824290912b6300ede2b2f95ff77d55dde9859b) Fix submodule parsing
+fix: [commit](https://codeberg.org/forgejo/forgejo/commit/ddabba5f89c4b196daeeb2af17de9ec2cec14b63) allow the actions user to login via the jwt token
+feat: [commit](https://codeberg.org/forgejo/forgejo/commit/262c48409b1224e3f6dc63c8d1e04fef0e0cf2c0) Support HTTP POST requests to `/userinfo`, aligning to OpenID Core specification