diff --git a/models/activities/action_list.go b/models/activities/action_list.go
index 16fb4bac8..f349b94ac 100644
--- a/models/activities/action_list.go
+++ b/models/activities/action_list.go
@@ -18,13 +18,11 @@ import (
 type ActionList []*Action
 
 func (actions ActionList) getUserIDs() []int64 {
-	userIDs := make(map[int64]struct{}, len(actions))
+	userIDs := make(container.Set[int64], len(actions))
 	for _, action := range actions {
-		if _, ok := userIDs[action.ActUserID]; !ok {
-			userIDs[action.ActUserID] = struct{}{}
-		}
+		userIDs.Add(action.ActUserID)
 	}
-	return container.KeysInt64(userIDs)
+	return userIDs.Values()
 }
 
 func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) {
@@ -48,13 +46,11 @@ func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.
 }
 
 func (actions ActionList) getRepoIDs() []int64 {
-	repoIDs := make(map[int64]struct{}, len(actions))
+	repoIDs := make(container.Set[int64], len(actions))
 	for _, action := range actions {
-		if _, ok := repoIDs[action.RepoID]; !ok {
-			repoIDs[action.RepoID] = struct{}{}
-		}
+		repoIDs.Add(action.RepoID)
 	}
-	return container.KeysInt64(repoIDs)
+	return repoIDs.Values()
 }
 
 func (actions ActionList) loadRepositories(ctx context.Context) error {
diff --git a/models/activities/notification.go b/models/activities/notification.go
index 88776db42..2f21dc74d 100644
--- a/models/activities/notification.go
+++ b/models/activities/notification.go
@@ -200,7 +200,7 @@ func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID,
 
 func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
 	// init
-	var toNotify map[int64]struct{}
+	var toNotify container.Set[int64]
 	notifications, err := getNotificationsByIssueID(ctx, issueID)
 	if err != nil {
 		return err
@@ -212,33 +212,27 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
 	}
 
 	if receiverID > 0 {
-		toNotify = make(map[int64]struct{}, 1)
-		toNotify[receiverID] = struct{}{}
+		toNotify = make(container.Set[int64], 1)
+		toNotify.Add(receiverID)
 	} else {
-		toNotify = make(map[int64]struct{}, 32)
+		toNotify = make(container.Set[int64], 32)
 		issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true)
 		if err != nil {
 			return err
 		}
-		for _, id := range issueWatches {
-			toNotify[id] = struct{}{}
-		}
+		toNotify.AddMultiple(issueWatches...)
 		if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) {
 			repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID)
 			if err != nil {
 				return err
 			}
-			for _, id := range repoWatches {
-				toNotify[id] = struct{}{}
-			}
+			toNotify.AddMultiple(repoWatches...)
 		}
 		issueParticipants, err := issue.GetParticipantIDsByIssue(ctx)
 		if err != nil {
 			return err
 		}
-		for _, id := range issueParticipants {
-			toNotify[id] = struct{}{}
-		}
+		toNotify.AddMultiple(issueParticipants...)
 
 		// dont notify user who cause notification
 		delete(toNotify, notificationAuthorID)
@@ -248,7 +242,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
 			return err
 		}
 		for _, id := range issueUnWatches {
-			delete(toNotify, id)
+			toNotify.Remove(id)
 		}
 	}
 
@@ -499,16 +493,14 @@ func (nl NotificationList) LoadAttributes() error {
 }
 
 func (nl NotificationList) getPendingRepoIDs() []int64 {
-	ids := make(map[int64]struct{}, len(nl))
+	ids := make(container.Set[int64], len(nl))
 	for _, notification := range nl {
 		if notification.Repository != nil {
 			continue
 		}
-		if _, ok := ids[notification.RepoID]; !ok {
-			ids[notification.RepoID] = struct{}{}
-		}
+		ids.Add(notification.RepoID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // LoadRepos loads repositories from database
@@ -575,16 +567,14 @@ func (nl NotificationList) LoadRepos() (repo_model.RepositoryList, []int, error)
 }
 
 func (nl NotificationList) getPendingIssueIDs() []int64 {
-	ids := make(map[int64]struct{}, len(nl))
+	ids := make(container.Set[int64], len(nl))
 	for _, notification := range nl {
 		if notification.Issue != nil {
 			continue
 		}
-		if _, ok := ids[notification.IssueID]; !ok {
-			ids[notification.IssueID] = struct{}{}
-		}
+		ids.Add(notification.IssueID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // LoadIssues loads issues from database
@@ -661,16 +651,14 @@ func (nl NotificationList) Without(failures []int) NotificationList {
 }
 
 func (nl NotificationList) getPendingCommentIDs() []int64 {
-	ids := make(map[int64]struct{}, len(nl))
+	ids := make(container.Set[int64], len(nl))
 	for _, notification := range nl {
 		if notification.CommentID == 0 || notification.Comment != nil {
 			continue
 		}
-		if _, ok := ids[notification.CommentID]; !ok {
-			ids[notification.CommentID] = struct{}{}
-		}
+		ids.Add(notification.CommentID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // LoadComments loads comments from database
diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go
index e3406a5cb..70105d7ff 100644
--- a/models/issues/comment_list.go
+++ b/models/issues/comment_list.go
@@ -17,13 +17,11 @@ import (
 type CommentList []*Comment
 
 func (comments CommentList) getPosterIDs() []int64 {
-	posterIDs := make(map[int64]struct{}, len(comments))
+	posterIDs := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := posterIDs[comment.PosterID]; !ok {
-			posterIDs[comment.PosterID] = struct{}{}
-		}
+		posterIDs.Add(comment.PosterID)
 	}
-	return container.KeysInt64(posterIDs)
+	return posterIDs.Values()
 }
 
 func (comments CommentList) loadPosters(ctx context.Context) error {
@@ -70,13 +68,11 @@ func (comments CommentList) getCommentIDs() []int64 {
 }
 
 func (comments CommentList) getLabelIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.LabelID]; !ok {
-			ids[comment.LabelID] = struct{}{}
-		}
+		ids.Add(comment.LabelID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadLabels(ctx context.Context) error { //nolint
@@ -120,13 +116,11 @@ func (comments CommentList) loadLabels(ctx context.Context) error { //nolint
 }
 
 func (comments CommentList) getMilestoneIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.MilestoneID]; !ok {
-			ids[comment.MilestoneID] = struct{}{}
-		}
+		ids.Add(comment.MilestoneID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadMilestones(ctx context.Context) error {
@@ -163,13 +157,11 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
 }
 
 func (comments CommentList) getOldMilestoneIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.OldMilestoneID]; !ok {
-			ids[comment.OldMilestoneID] = struct{}{}
-		}
+		ids.Add(comment.OldMilestoneID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadOldMilestones(ctx context.Context) error {
@@ -206,13 +198,11 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
 }
 
 func (comments CommentList) getAssigneeIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.AssigneeID]; !ok {
-			ids[comment.AssigneeID] = struct{}{}
-		}
+		ids.Add(comment.AssigneeID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadAssignees(ctx context.Context) error {
@@ -259,16 +249,14 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
 
 // getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
 func (comments CommentList) getIssueIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
 		if comment.Issue != nil {
 			continue
 		}
-		if _, ok := ids[comment.IssueID]; !ok {
-			ids[comment.IssueID] = struct{}{}
-		}
+		ids.Add(comment.IssueID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // Issues returns all the issues of comments
@@ -334,16 +322,14 @@ func (comments CommentList) loadIssues(ctx context.Context) error {
 }
 
 func (comments CommentList) getDependentIssueIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
 		if comment.DependentIssue != nil {
 			continue
 		}
-		if _, ok := ids[comment.DependentIssueID]; !ok {
-			ids[comment.DependentIssueID] = struct{}{}
-		}
+		ids.Add(comment.DependentIssueID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadDependentIssues(ctx context.Context) error {
@@ -439,13 +425,11 @@ func (comments CommentList) loadAttachments(ctx context.Context) (err error) {
 }
 
 func (comments CommentList) getReviewIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.ReviewID]; !ok {
-			ids[comment.ReviewID] = struct{}{}
-		}
+		ids.Add(comment.ReviewID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadReviews(ctx context.Context) error { //nolint
diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go
index 874f2a636..deadb6a56 100644
--- a/models/issues/issue_list.go
+++ b/models/issues/issue_list.go
@@ -22,16 +22,16 @@ type IssueList []*Issue
 
 // get the repo IDs to be loaded later, these IDs are for issue.Repo and issue.PullRequest.HeadRepo
 func (issues IssueList) getRepoIDs() []int64 {
-	repoIDs := make(map[int64]struct{}, len(issues))
+	repoIDs := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
 		if issue.Repo == nil {
-			repoIDs[issue.RepoID] = struct{}{}
+			repoIDs.Add(issue.RepoID)
 		}
 		if issue.PullRequest != nil && issue.PullRequest.HeadRepo == nil {
-			repoIDs[issue.PullRequest.HeadRepoID] = struct{}{}
+			repoIDs.Add(issue.PullRequest.HeadRepoID)
 		}
 	}
-	return container.KeysInt64(repoIDs)
+	return repoIDs.Values()
 }
 
 func (issues IssueList) loadRepositories(ctx context.Context) ([]*repo_model.Repository, error) {
@@ -79,13 +79,11 @@ func (issues IssueList) LoadRepositories() ([]*repo_model.Repository, error) {
 }
 
 func (issues IssueList) getPosterIDs() []int64 {
-	posterIDs := make(map[int64]struct{}, len(issues))
+	posterIDs := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
-		if _, ok := posterIDs[issue.PosterID]; !ok {
-			posterIDs[issue.PosterID] = struct{}{}
-		}
+		posterIDs.Add(issue.PosterID)
 	}
-	return container.KeysInt64(posterIDs)
+	return posterIDs.Values()
 }
 
 func (issues IssueList) loadPosters(ctx context.Context) error {
@@ -185,13 +183,11 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
 }
 
 func (issues IssueList) getMilestoneIDs() []int64 {
-	ids := make(map[int64]struct{}, len(issues))
+	ids := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
-		if _, ok := ids[issue.MilestoneID]; !ok {
-			ids[issue.MilestoneID] = struct{}{}
-		}
+		ids.Add(issue.MilestoneID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (issues IssueList) loadMilestones(ctx context.Context) error {
@@ -224,14 +220,11 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
 }
 
 func (issues IssueList) getProjectIDs() []int64 {
-	ids := make(map[int64]struct{}, len(issues))
+	ids := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
-		projectID := issue.ProjectID()
-		if _, ok := ids[projectID]; !ok {
-			ids[projectID] = struct{}{}
-		}
+		ids.Add(issue.ProjectID())
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (issues IssueList) loadProjects(ctx context.Context) error {
diff --git a/models/issues/reaction.go b/models/issues/reaction.go
index e7295c8af..ccda10be2 100644
--- a/models/issues/reaction.go
+++ b/models/issues/reaction.go
@@ -211,7 +211,7 @@ type ReactionOptions struct {
 
 // CreateReaction creates reaction for issue or comment.
 func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
-	if !setting.UI.ReactionsMap[opts.Type] {
+	if !setting.UI.ReactionsLookup.Contains(opts.Type) {
 		return nil, ErrForbiddenIssueReaction{opts.Type}
 	}
 
@@ -316,16 +316,14 @@ func (list ReactionList) GroupByType() map[string]ReactionList {
 }
 
 func (list ReactionList) getUserIDs() []int64 {
-	userIDs := make(map[int64]struct{}, len(list))
+	userIDs := make(container.Set[int64], len(list))
 	for _, reaction := range list {
 		if reaction.OriginalAuthor != "" {
 			continue
 		}
-		if _, ok := userIDs[reaction.UserID]; !ok {
-			userIDs[reaction.UserID] = struct{}{}
-		}
+		userIDs.Add(reaction.UserID)
 	}
-	return container.KeysInt64(userIDs)
+	return userIDs.Values()
 }
 
 func valuesUser(m map[int64]*user_model.User) []*user_model.User {
diff --git a/models/migrate.go b/models/migrate.go
index f6bceaa01..d842fb967 100644
--- a/models/migrate.go
+++ b/models/migrate.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/structs"
 )
 
@@ -99,9 +100,9 @@ func InsertIssueComments(comments []*issues_model.Comment) error {
 		return nil
 	}
 
-	issueIDs := make(map[int64]bool)
+	issueIDs := make(container.Set[int64])
 	for _, comment := range comments {
-		issueIDs[comment.IssueID] = true
+		issueIDs.Add(comment.IssueID)
 	}
 
 	ctx, committer, err := db.TxContext()
diff --git a/models/migrations/v115.go b/models/migrations/v115.go
index 7708ed5e2..b242ccf23 100644
--- a/models/migrations/v115.go
+++ b/models/migrations/v115.go
@@ -13,6 +13,7 @@ import (
 	"path/filepath"
 	"time"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
@@ -39,7 +40,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 	}
 	log.Info("%d User Avatar(s) to migrate ...", count)
 
-	deleteList := make(map[string]struct{})
+	deleteList := make(container.Set[string])
 	start := 0
 	migrated := 0
 	for {
@@ -86,7 +87,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 				return fmt.Errorf("[user: %s] user table update: %v", user.LowerName, err)
 			}
 
-			deleteList[filepath.Join(setting.Avatar.Path, oldAvatar)] = struct{}{}
+			deleteList.Add(filepath.Join(setting.Avatar.Path, oldAvatar))
 			migrated++
 			select {
 			case <-ticker.C:
diff --git a/models/packages/conan/search.go b/models/packages/conan/search.go
index 6a2cfa38f..39a900045 100644
--- a/models/packages/conan/search.go
+++ b/models/packages/conan/search.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/packages"
+	"code.gitea.io/gitea/modules/container"
 	conan_module "code.gitea.io/gitea/modules/packages/conan"
 
 	"xorm.io/builder"
@@ -88,7 +89,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
 		return nil, err
 	}
 
-	unique := make(map[string]bool)
+	unique := make(container.Set[string])
 	for _, info := range results {
 		recipe := fmt.Sprintf("%s/%s", info.Name, info.Version)
 
@@ -111,7 +112,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
 			}
 		}
 
-		unique[recipe] = true
+		unique.Add(recipe)
 	}
 
 	recipes := make([]string, 0, len(unique))
diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go
index cc524a417..0cd0a3c8e 100644
--- a/models/repo/repo_list.go
+++ b/models/repo/repo_list.go
@@ -68,10 +68,10 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
 		return nil
 	}
 
-	set := make(map[int64]struct{})
+	set := make(container.Set[int64])
 	repoIDs := make([]int64, len(repos))
 	for i := range repos {
-		set[repos[i].OwnerID] = struct{}{}
+		set.Add(repos[i].OwnerID)
 		repoIDs[i] = repos[i].ID
 	}
 
@@ -79,7 +79,7 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
 	users := make(map[int64]*user_model.User, len(set))
 	if err := db.GetEngine(ctx).
 		Where("id > 0").
-		In("id", container.KeysInt64(set)).
+		In("id", set.Values()).
 		Find(&users); err != nil {
 		return fmt.Errorf("find users: %v", err)
 	}
diff --git a/models/repo/topic.go b/models/repo/topic.go
index 2a1646721..7ba9a49e8 100644
--- a/models/repo/topic.go
+++ b/models/repo/topic.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/timeutil"
 
 	"xorm.io/builder"
@@ -62,7 +63,7 @@ func ValidateTopic(topic string) bool {
 // SanitizeAndValidateTopics sanitizes and checks an array or topics
 func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []string) {
 	validTopics = make([]string, 0)
-	mValidTopics := make(map[string]struct{})
+	mValidTopics := make(container.Set[string])
 	invalidTopics = make([]string, 0)
 
 	for _, topic := range topics {
@@ -72,12 +73,12 @@ func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []st
 			continue
 		}
 		// ignore same topic twice
-		if _, ok := mValidTopics[topic]; ok {
+		if mValidTopics.Contains(topic) {
 			continue
 		}
 		if ValidateTopic(topic) {
 			validTopics = append(validTopics, topic)
-			mValidTopics[topic] = struct{}{}
+			mValidTopics.Add(topic)
 		} else {
 			invalidTopics = append(invalidTopics, topic)
 		}
diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go
index 6c0a241dc..e7125f70f 100644
--- a/models/repo/user_repo.go
+++ b/models/repo/user_repo.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/perm"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	api "code.gitea.io/gitea/modules/structs"
 
 	"xorm.io/builder"
@@ -83,37 +84,19 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
 		return nil, err
 	}
 
-	uidMap := map[int64]bool{}
-	i := 0
-	for _, uid := range userIDs {
-		if uidMap[uid] {
-			continue
-		}
-		uidMap[uid] = true
-		userIDs[i] = uid
-		i++
-	}
-	userIDs = userIDs[:i]
-	userIDs = append(userIDs, additionalUserIDs...)
-
-	for _, uid := range additionalUserIDs {
-		if uidMap[uid] {
-			continue
-		}
-		userIDs[i] = uid
-		i++
-	}
-	userIDs = userIDs[:i]
+	uniqueUserIDs := make(container.Set[int64])
+	uniqueUserIDs.AddMultiple(userIDs...)
+	uniqueUserIDs.AddMultiple(additionalUserIDs...)
 
 	// Leave a seat for owner itself to append later, but if owner is an organization
 	// and just waste 1 unit is cheaper than re-allocate memory once.
-	users := make([]*user_model.User, 0, len(userIDs)+1)
+	users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
 	if len(userIDs) > 0 {
-		if err = e.In("id", userIDs).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
+		if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
 			return nil, err
 		}
 	}
-	if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
+	if !repo.Owner.IsOrganization() && !uniqueUserIDs.Contains(repo.OwnerID) {
 		users = append(users, repo.Owner)
 	}
 
diff --git a/modules/base/tool.go b/modules/base/tool.go
index a981fd6c5..f1e4a3bf9 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -241,15 +241,6 @@ func Int64sToStrings(ints []int64) []string {
 	return strs
 }
 
-// Int64sToMap converts a slice of int64 to a int64 map.
-func Int64sToMap(ints []int64) map[int64]bool {
-	m := make(map[int64]bool)
-	for _, i := range ints {
-		m[i] = true
-	}
-	return m
-}
-
 // Int64sContains returns if a int64 in a slice of int64
 func Int64sContains(intsSlice []int64, a int64) bool {
 	for _, c := range intsSlice {
diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go
index 6685168ba..87de898e0 100644
--- a/modules/base/tool_test.go
+++ b/modules/base/tool_test.go
@@ -214,16 +214,7 @@ func TestInt64sToStrings(t *testing.T) {
 	)
 }
 
-func TestInt64sToMap(t *testing.T) {
-	assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
-	assert.Equal(t,
-		map[int64]bool{1: true, 4: true, 16: true},
-		Int64sToMap([]int64{1, 4, 16}),
-	)
-}
-
 func TestInt64sContains(t *testing.T) {
-	assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
 	assert.True(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 1))
 	assert.True(t, Int64sContains([]int64{2323}, 2323))
 	assert.False(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232))
diff --git a/modules/container/map.go b/modules/container/map.go
deleted file mode 100644
index 3519de095..000000000
--- a/modules/container/map.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package container
-
-// KeysInt64 returns keys slice for a map with int64 key
-func KeysInt64(m map[int64]struct{}) []int64 {
-	keys := make([]int64, 0, len(m))
-	for k := range m {
-		keys = append(keys, k)
-	}
-	return keys
-}
diff --git a/modules/container/set.go b/modules/container/set.go
new file mode 100644
index 000000000..4b4c74525
--- /dev/null
+++ b/modules/container/set.go
@@ -0,0 +1,57 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package container
+
+type Set[T comparable] map[T]struct{}
+
+// SetOf creates a set and adds the specified elements to it.
+func SetOf[T comparable](values ...T) Set[T] {
+	s := make(Set[T], len(values))
+	s.AddMultiple(values...)
+	return s
+}
+
+// Add adds the specified element to a set.
+// Returns true if the element is added; false if the element is already present.
+func (s Set[T]) Add(value T) bool {
+	if _, has := s[value]; !has {
+		s[value] = struct{}{}
+		return true
+	}
+	return false
+}
+
+// AddMultiple adds the specified elements to a set.
+func (s Set[T]) AddMultiple(values ...T) {
+	for _, value := range values {
+		s.Add(value)
+	}
+}
+
+// Contains determines whether a set contains the specified element.
+// Returns true if the set contains the specified element; otherwise, false.
+func (s Set[T]) Contains(value T) bool {
+	_, has := s[value]
+	return has
+}
+
+// Remove removes the specified element.
+// Returns true if the element is successfully found and removed; otherwise, false.
+func (s Set[T]) Remove(value T) bool {
+	if _, has := s[value]; has {
+		delete(s, value)
+		return true
+	}
+	return false
+}
+
+// Values gets a list of all elements in the set.
+func (s Set[T]) Values() []T {
+	keys := make([]T, 0, len(s))
+	for k := range s {
+		keys = append(keys, k)
+	}
+	return keys
+}
diff --git a/modules/container/set_test.go b/modules/container/set_test.go
new file mode 100644
index 000000000..6654763e5
--- /dev/null
+++ b/modules/container/set_test.go
@@ -0,0 +1,37 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package container
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSet(t *testing.T) {
+	s := make(Set[string])
+
+	assert.True(t, s.Add("key1"))
+	assert.False(t, s.Add("key1"))
+	assert.True(t, s.Add("key2"))
+
+	assert.True(t, s.Contains("key1"))
+	assert.True(t, s.Contains("key2"))
+	assert.False(t, s.Contains("key3"))
+
+	assert.True(t, s.Remove("key2"))
+	assert.False(t, s.Contains("key2"))
+
+	assert.False(t, s.Remove("key3"))
+
+	s.AddMultiple("key4", "key5")
+	assert.True(t, s.Contains("key4"))
+	assert.True(t, s.Contains("key5"))
+
+	s = SetOf("key6", "key7")
+	assert.False(t, s.Contains("key1"))
+	assert.True(t, s.Contains("key6"))
+	assert.True(t, s.Contains("key7"))
+}
diff --git a/modules/doctor/authorizedkeys.go b/modules/doctor/authorizedkeys.go
index 34dfe939d..d4ceef87c 100644
--- a/modules/doctor/authorizedkeys.go
+++ b/modules/doctor/authorizedkeys.go
@@ -14,6 +14,7 @@ import (
 	"strings"
 
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -40,7 +41,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 	}
 	defer f.Close()
 
-	linesInAuthorizedKeys := map[string]bool{}
+	linesInAuthorizedKeys := make(container.Set[string])
 
 	scanner := bufio.NewScanner(f)
 	for scanner.Scan() {
@@ -48,7 +49,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 		if strings.HasPrefix(line, tplCommentPrefix) {
 			continue
 		}
-		linesInAuthorizedKeys[line] = true
+		linesInAuthorizedKeys.Add(line)
 	}
 	f.Close()
 
@@ -64,7 +65,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 		if strings.HasPrefix(line, tplCommentPrefix) {
 			continue
 		}
-		if ok := linesInAuthorizedKeys[line]; ok {
+		if linesInAuthorizedKeys.Contains(line) {
 			continue
 		}
 		if !autofix {
diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go
index 80f160270..469932525 100644
--- a/modules/git/log_name_status.go
+++ b/modules/git/log_name_status.go
@@ -14,6 +14,8 @@ import (
 	"sort"
 	"strings"
 
+	"code.gitea.io/gitea/modules/container"
+
 	"github.com/djherbis/buffer"
 	"github.com/djherbis/nio/v3"
 )
@@ -339,7 +341,7 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st
 	lastEmptyParent := head.ID.String()
 	commitSinceLastEmptyParent := uint64(0)
 	commitSinceNextRestart := uint64(0)
-	parentRemaining := map[string]bool{}
+	parentRemaining := make(container.Set[string])
 
 	changed := make([]bool, len(paths))
 
@@ -365,7 +367,7 @@ heaploop:
 		if current == nil {
 			break heaploop
 		}
-		delete(parentRemaining, current.CommitID)
+		parentRemaining.Remove(current.CommitID)
 		if current.Paths != nil {
 			for i, found := range current.Paths {
 				if !found {
@@ -410,14 +412,12 @@ heaploop:
 					}
 				}
 				g = NewLogNameStatusRepoParser(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...)
-				parentRemaining = map[string]bool{}
+				parentRemaining = make(container.Set[string])
 				nextRestart = (remaining * 3) / 4
 				continue heaploop
 			}
 		}
-		for _, parent := range current.ParentIDs {
-			parentRemaining[parent] = true
-		}
+		parentRemaining.AddMultiple(current.ParentIDs...)
 	}
 	g.Close()
 
diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go
index c0c91c6fc..cb44c58ce 100644
--- a/modules/git/repo_stats.go
+++ b/modules/git/repo_stats.go
@@ -13,6 +13,8 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"code.gitea.io/gitea/modules/container"
 )
 
 // CodeActivityStats represents git statistics data
@@ -80,7 +82,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
 			stats.Additions = 0
 			stats.Deletions = 0
 			authors := make(map[string]*CodeActivityAuthor)
-			files := make(map[string]bool)
+			files := make(container.Set[string])
 			var author string
 			p := 0
 			for scanner.Scan() {
@@ -119,9 +121,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
 								stats.Deletions += c
 							}
 						}
-						if _, ok := files[parts[2]]; !ok {
-							files[parts[2]] = true
-						}
+						files.Add(parts[2])
 					}
 				}
 			}
diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go
index a4c0fb5aa..3b33852cb 100644
--- a/modules/issue/template/template.go
+++ b/modules/issue/template/template.go
@@ -11,6 +11,7 @@ import (
 	"strconv"
 	"strings"
 
+	"code.gitea.io/gitea/modules/container"
 	api "code.gitea.io/gitea/modules/structs"
 
 	"gitea.com/go-chi/binding"
@@ -43,7 +44,7 @@ func validateYaml(template *api.IssueTemplate) error {
 	if len(template.Fields) == 0 {
 		return fmt.Errorf("'body' is required")
 	}
-	ids := map[string]struct{}{}
+	ids := make(container.Set[string])
 	for idx, field := range template.Fields {
 		if err := validateID(field, idx, ids); err != nil {
 			return err
@@ -125,7 +126,7 @@ func validateRequired(field *api.IssueFormField, idx int) error {
 	return validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required")
 }
 
-func validateID(field *api.IssueFormField, idx int, ids map[string]struct{}) error {
+func validateID(field *api.IssueFormField, idx int, ids container.Set[string]) error {
 	if field.Type == api.IssueFormFieldTypeMarkdown {
 		// The ID is not required for a markdown field
 		return nil
@@ -139,10 +140,9 @@ func validateID(field *api.IssueFormField, idx int, ids map[string]struct{}) err
 	if binding.AlphaDashPattern.MatchString(field.ID) {
 		return position.Errorf("'id' should contain only alphanumeric, '-' and '_'")
 	}
-	if _, ok := ids[field.ID]; ok {
+	if !ids.Add(field.ID) {
 		return position.Errorf("'id' should be unique")
 	}
-	ids[field.ID] = struct{}{}
 	return nil
 }
 
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 24f1ab7a0..8417019dd 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -10,6 +10,7 @@ import (
 	"regexp"
 	"strings"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/common"
 	"code.gitea.io/gitea/modules/setting"
@@ -198,7 +199,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 }
 
 type prefixedIDs struct {
-	values map[string]bool
+	values container.Set[string]
 }
 
 // Generate generates a new element id.
@@ -219,14 +220,12 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
 	if !bytes.HasPrefix(result, []byte("user-content-")) {
 		result = append([]byte("user-content-"), result...)
 	}
-	if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
-		p.values[util.BytesToReadOnlyString(result)] = true
+	if p.values.Add(util.BytesToReadOnlyString(result)) {
 		return result
 	}
 	for i := 1; ; i++ {
 		newResult := fmt.Sprintf("%s-%d", result, i)
-		if _, ok := p.values[newResult]; !ok {
-			p.values[newResult] = true
+		if p.values.Add(newResult) {
 			return []byte(newResult)
 		}
 	}
@@ -234,12 +233,12 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
 
 // Put puts a given element id to the used ids table.
 func (p *prefixedIDs) Put(value []byte) {
-	p.values[util.BytesToReadOnlyString(value)] = true
+	p.values.Add(util.BytesToReadOnlyString(value))
 }
 
 func newPrefixedIDs() *prefixedIDs {
 	return &prefixedIDs{
-		values: map[string]bool{},
+		values: make(container.Set[string]),
 	}
 }
 
diff --git a/modules/notification/ui/ui.go b/modules/notification/ui/ui.go
index 5e5196a70..4d96a6b0e 100644
--- a/modules/notification/ui/ui.go
+++ b/modules/notification/ui/ui.go
@@ -10,6 +10,7 @@ import (
 	issues_model "code.gitea.io/gitea/models/issues"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/notification/base"
@@ -123,14 +124,14 @@ func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest
 		log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err)
 		return
 	}
-	toNotify := make(map[int64]struct{}, 32)
+	toNotify := make(container.Set[int64], 32)
 	repoWatchers, err := repo_model.GetRepoWatchersIDs(db.DefaultContext, pr.Issue.RepoID)
 	if err != nil {
 		log.Error("GetRepoWatchersIDs: %v", err)
 		return
 	}
 	for _, id := range repoWatchers {
-		toNotify[id] = struct{}{}
+		toNotify.Add(id)
 	}
 	issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(pr.IssueID)
 	if err != nil {
@@ -138,11 +139,11 @@ func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest
 		return
 	}
 	for _, id := range issueParticipants {
-		toNotify[id] = struct{}{}
+		toNotify.Add(id)
 	}
 	delete(toNotify, pr.Issue.PosterID)
 	for _, mention := range mentions {
-		toNotify[mention.ID] = struct{}{}
+		toNotify.Add(mention.ID)
 	}
 	for receiverID := range toNotify {
 		_ = ns.issueQueue.Push(issueNotificationOpts{
diff --git a/modules/public/public.go b/modules/public/public.go
index 7804e945e..ac1d80c86 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -11,6 +11,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -83,11 +84,11 @@ func AssetsHandlerFunc(opts *Options) http.HandlerFunc {
 }
 
 // parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods
-func parseAcceptEncoding(val string) map[string]bool {
+func parseAcceptEncoding(val string) container.Set[string] {
 	parts := strings.Split(val, ";")
-	types := make(map[string]bool)
+	types := make(container.Set[string])
 	for _, v := range strings.Split(parts[0], ",") {
-		types[strings.TrimSpace(v)] = true
+		types.Add(strings.TrimSpace(v))
 	}
 	return types
 }
diff --git a/modules/public/public_test.go b/modules/public/public_test.go
index 430e73456..8b58d6af3 100644
--- a/modules/public/public_test.go
+++ b/modules/public/public_test.go
@@ -7,28 +7,23 @@ package public
 import (
 	"testing"
 
+	"code.gitea.io/gitea/modules/container"
+
 	"github.com/stretchr/testify/assert"
 )
 
 func TestParseAcceptEncoding(t *testing.T) {
 	kases := []struct {
 		Header   string
-		Expected map[string]bool
+		Expected container.Set[string]
 	}{
 		{
-			Header: "deflate, gzip;q=1.0, *;q=0.5",
-			Expected: map[string]bool{
-				"deflate": true,
-				"gzip":    true,
-			},
+			Header:   "deflate, gzip;q=1.0, *;q=0.5",
+			Expected: container.SetOf("deflate", "gzip"),
 		},
 		{
-			Header: " gzip, deflate, br",
-			Expected: map[string]bool{
-				"deflate": true,
-				"gzip":    true,
-				"br":      true,
-			},
+			Header:   " gzip, deflate, br",
+			Expected: container.SetOf("deflate", "gzip", "br"),
 		},
 	}
 
diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go
index 9666880ad..10120bf85 100644
--- a/modules/public/serve_static.go
+++ b/modules/public/serve_static.go
@@ -60,7 +60,7 @@ func AssetIsDir(name string) (bool, error) {
 // serveContent serve http content
 func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) {
 	encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
-	if encodings["gzip"] {
+	if encodings.Contains("gzip") {
 		if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
 			rdGzip := bytes.NewReader(cf.GzipBytes())
 			// all static files are managed by Gitea, so we can make sure every file has the correct ext name
diff --git a/modules/queue/unique_queue_channel.go b/modules/queue/unique_queue_channel.go
index 6e8d37a20..d1bf7239e 100644
--- a/modules/queue/unique_queue_channel.go
+++ b/modules/queue/unique_queue_channel.go
@@ -12,6 +12,7 @@ import (
 	"sync/atomic"
 	"time"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 )
@@ -33,7 +34,7 @@ type ChannelUniqueQueueConfiguration ChannelQueueConfiguration
 type ChannelUniqueQueue struct {
 	*WorkerPool
 	lock               sync.Mutex
-	table              map[string]bool
+	table              container.Set[string]
 	shutdownCtx        context.Context
 	shutdownCtxCancel  context.CancelFunc
 	terminateCtx       context.Context
@@ -58,7 +59,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue
 	shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx)
 
 	queue := &ChannelUniqueQueue{
-		table:              map[string]bool{},
+		table:              make(container.Set[string]),
 		shutdownCtx:        shutdownCtx,
 		shutdownCtxCancel:  shutdownCtxCancel,
 		terminateCtx:       terminateCtx,
@@ -73,7 +74,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue
 			bs, _ := json.Marshal(datum)
 
 			queue.lock.Lock()
-			delete(queue.table, string(bs))
+			queue.table.Remove(string(bs))
 			queue.lock.Unlock()
 
 			if u := handle(datum); u != nil {
@@ -127,16 +128,15 @@ func (q *ChannelUniqueQueue) PushFunc(data Data, fn func() error) error {
 			q.lock.Unlock()
 		}
 	}()
-	if _, ok := q.table[string(bs)]; ok {
+	if !q.table.Add(string(bs)) {
 		return ErrAlreadyInQueue
 	}
 	// FIXME: We probably need to implement some sort of limit here
 	// If the downstream queue blocks this table will grow without limit
-	q.table[string(bs)] = true
 	if fn != nil {
 		err := fn()
 		if err != nil {
-			delete(q.table, string(bs))
+			q.table.Remove(string(bs))
 			return err
 		}
 	}
@@ -155,8 +155,7 @@ func (q *ChannelUniqueQueue) Has(data Data) (bool, error) {
 
 	q.lock.Lock()
 	defer q.lock.Unlock()
-	_, has := q.table[string(bs)]
-	return has, nil
+	return q.table.Contains(string(bs)), nil
 }
 
 // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index 48c3edf60..b01be322d 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -18,6 +18,7 @@ import (
 	"code.gitea.io/gitea/models/organization"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
@@ -275,7 +276,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
 		return pullMirrorReleaseSync(repo, gitRepo)
 	}
 
-	existingRelTags := make(map[string]struct{})
+	existingRelTags := make(container.Set[string])
 	opts := repo_model.FindReleasesOptions{
 		IncludeDrafts: true,
 		IncludeTags:   true,
@@ -303,14 +304,14 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
 					return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
 				}
 			} else {
-				existingRelTags[strings.ToLower(rel.TagName)] = struct{}{}
+				existingRelTags.Add(strings.ToLower(rel.TagName))
 			}
 		}
 	}
 
 	_, err := gitRepo.WalkReferences(git.ObjectTag, 0, 0, func(sha1, refname string) error {
 		tagName := strings.TrimPrefix(refname, git.TagPrefix)
-		if _, ok := existingRelTags[strings.ToLower(tagName)]; ok {
+		if existingRelTags.Contains(strings.ToLower(tagName)) {
 			return nil
 		}
 
diff --git a/modules/setting/queue.go b/modules/setting/queue.go
index cb86cbdfe..d3bb33b24 100644
--- a/modules/setting/queue.go
+++ b/modules/setting/queue.go
@@ -9,6 +9,7 @@ import (
 	"strconv"
 	"time"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 
 	ini "gopkg.in/ini.v1"
@@ -109,8 +110,8 @@ func NewQueueService() {
 	// Now handle the old issue_indexer configuration
 	// FIXME: DEPRECATED to be removed in v1.18.0
 	section := Cfg.Section("queue.issue_indexer")
-	directlySet := toDirectlySetKeysMap(section)
-	if !directlySet["TYPE"] && defaultType == "" {
+	directlySet := toDirectlySetKeysSet(section)
+	if !directlySet.Contains("TYPE") && defaultType == "" {
 		switch typ := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_TYPE").MustString(""); typ {
 		case "levelqueue":
 			_, _ = section.NewKey("TYPE", "level")
@@ -124,25 +125,25 @@ func NewQueueService() {
 			log.Fatal("Unsupported indexer queue type: %v", typ)
 		}
 	}
-	if !directlySet["LENGTH"] {
+	if !directlySet.Contains("LENGTH") {
 		length := Cfg.Section("indexer").Key("UPDATE_BUFFER_LEN").MustInt(0)
 		if length != 0 {
 			_, _ = section.NewKey("LENGTH", strconv.Itoa(length))
 		}
 	}
-	if !directlySet["BATCH_LENGTH"] {
+	if !directlySet.Contains("BATCH_LENGTH") {
 		fallback := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_BATCH_NUMBER").MustInt(0)
 		if fallback != 0 {
 			_, _ = section.NewKey("BATCH_LENGTH", strconv.Itoa(fallback))
 		}
 	}
-	if !directlySet["DATADIR"] {
+	if !directlySet.Contains("DATADIR") {
 		queueDir := filepath.ToSlash(Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_DIR").MustString(""))
 		if queueDir != "" {
 			_, _ = section.NewKey("DATADIR", queueDir)
 		}
 	}
-	if !directlySet["CONN_STR"] {
+	if !directlySet.Contains("CONN_STR") {
 		connStr := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString("")
 		if connStr != "" {
 			_, _ = section.NewKey("CONN_STR", connStr)
@@ -178,19 +179,19 @@ func handleOldLengthConfiguration(queueName, oldSection, oldKey string, defaultV
 	}
 
 	section := Cfg.Section("queue." + queueName)
-	directlySet := toDirectlySetKeysMap(section)
-	if !directlySet["LENGTH"] {
+	directlySet := toDirectlySetKeysSet(section)
+	if !directlySet.Contains("LENGTH") {
 		_, _ = section.NewKey("LENGTH", strconv.Itoa(value))
 	}
 }
 
-// toDirectlySetKeysMap returns a bool map of keys directly set by this section
+// toDirectlySetKeysSet returns a set of keys directly set by this section
 // Note: we cannot use section.HasKey(...) as that will immediately set the Key if a parent section has the Key
 // but this section does not.
-func toDirectlySetKeysMap(section *ini.Section) map[string]bool {
-	sectionMap := map[string]bool{}
+func toDirectlySetKeysSet(section *ini.Section) container.Set[string] {
+	sections := make(container.Set[string])
 	for _, key := range section.Keys() {
-		sectionMap[key.Name()] = true
+		sections.Add(key.Name())
 	}
-	return sectionMap
+	return sections
 }
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 6233437bf..007e3ef61 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -21,6 +21,7 @@ import (
 	"text/template"
 	"time"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/user"
@@ -234,7 +235,7 @@ var (
 		DefaultTheme          string
 		Themes                []string
 		Reactions             []string
-		ReactionsMap          map[string]bool `ini:"-"`
+		ReactionsLookup       container.Set[string] `ini:"-"`
 		CustomEmojis          []string
 		CustomEmojisMap       map[string]string `ini:"-"`
 		SearchRepoDescription bool
@@ -1100,9 +1101,9 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
 
 	newMarkup()
 
-	UI.ReactionsMap = make(map[string]bool)
+	UI.ReactionsLookup = make(container.Set[string])
 	for _, reaction := range UI.Reactions {
-		UI.ReactionsMap[reaction] = true
+		UI.ReactionsLookup.Add(reaction)
 	}
 	UI.CustomEmojisMap = make(map[string]string)
 	for _, emoji := range UI.CustomEmojis {
diff --git a/modules/sync/status_pool.go b/modules/sync/status_pool.go
index acbd93ab1..99e5ce9cb 100644
--- a/modules/sync/status_pool.go
+++ b/modules/sync/status_pool.go
@@ -6,6 +6,8 @@ package sync
 
 import (
 	"sync"
+
+	"code.gitea.io/gitea/modules/container"
 )
 
 // StatusTable is a table maintains true/false values.
@@ -14,13 +16,13 @@ import (
 // in different goroutines.
 type StatusTable struct {
 	lock sync.RWMutex
-	pool map[string]struct{}
+	pool container.Set[string]
 }
 
 // NewStatusTable initializes and returns a new StatusTable object.
 func NewStatusTable() *StatusTable {
 	return &StatusTable{
-		pool: make(map[string]struct{}),
+		pool: make(container.Set[string]),
 	}
 }
 
@@ -28,32 +30,29 @@ func NewStatusTable() *StatusTable {
 // Returns whether set value was set to true
 func (p *StatusTable) StartIfNotRunning(name string) bool {
 	p.lock.Lock()
-	_, ok := p.pool[name]
-	if !ok {
-		p.pool[name] = struct{}{}
-	}
+	added := p.pool.Add(name)
 	p.lock.Unlock()
-	return !ok
+	return added
 }
 
 // Start sets value of given name to true in the pool.
 func (p *StatusTable) Start(name string) {
 	p.lock.Lock()
-	p.pool[name] = struct{}{}
+	p.pool.Add(name)
 	p.lock.Unlock()
 }
 
 // Stop sets value of given name to false in the pool.
 func (p *StatusTable) Stop(name string) {
 	p.lock.Lock()
-	delete(p.pool, name)
+	p.pool.Remove(name)
 	p.lock.Unlock()
 }
 
 // IsRunning checks if value of given name is set to true in the pool.
 func (p *StatusTable) IsRunning(name string) bool {
 	p.lock.RLock()
-	_, ok := p.pool[name]
+	exists := p.pool.Contains(name)
 	p.lock.RUnlock()
-	return ok
+	return exists
 }
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index c57f80ac2..dd078d6ad 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
 	conan_model "code.gitea.io/gitea/models/packages/conan"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
@@ -33,20 +34,18 @@ const (
 	packageReferenceKey = "PackageReference"
 )
 
-type stringSet map[string]struct{}
-
 var (
-	recipeFileList = stringSet{
-		conanfileFile:       struct{}{},
-		"conanmanifest.txt": struct{}{},
-		"conan_sources.tgz": struct{}{},
-		"conan_export.tgz":  struct{}{},
-	}
-	packageFileList = stringSet{
-		conaninfoFile:       struct{}{},
-		"conanmanifest.txt": struct{}{},
-		"conan_package.tgz": struct{}{},
-	}
+	recipeFileList = container.SetOf(
+		conanfileFile,
+		"conanmanifest.txt",
+		"conan_sources.tgz",
+		"conan_export.tgz",
+	)
+	packageFileList = container.SetOf(
+		conaninfoFile,
+		"conanmanifest.txt",
+		"conan_package.tgz",
+	)
 )
 
 func jsonResponse(ctx *context.Context, status int, obj interface{}) {
@@ -268,7 +267,7 @@ func PackageUploadURLs(ctx *context.Context) {
 	)
 }
 
-func serveUploadURLs(ctx *context.Context, fileFilter stringSet, uploadURL string) {
+func serveUploadURLs(ctx *context.Context, fileFilter container.Set[string], uploadURL string) {
 	defer ctx.Req.Body.Close()
 
 	var files map[string]int64
@@ -279,7 +278,7 @@ func serveUploadURLs(ctx *context.Context, fileFilter stringSet, uploadURL strin
 
 	urls := make(map[string]string)
 	for file := range files {
-		if _, ok := fileFilter[file]; ok {
+		if fileFilter.Contains(file) {
 			urls[file] = fmt.Sprintf("%s/%s", uploadURL, file)
 		}
 	}
@@ -301,12 +300,12 @@ func UploadPackageFile(ctx *context.Context) {
 	uploadFile(ctx, packageFileList, pref.AsKey())
 }
 
-func uploadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
+func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
 	rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
 	pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
 
 	filename := ctx.Params("filename")
-	if _, ok := fileFilter[filename]; !ok {
+	if !fileFilter.Contains(filename) {
 		apiError(ctx, http.StatusBadRequest, nil)
 		return
 	}
@@ -442,11 +441,11 @@ func DownloadPackageFile(ctx *context.Context) {
 	downloadFile(ctx, packageFileList, pref.AsKey())
 }
 
-func downloadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
+func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
 	rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
 
 	filename := ctx.Params("filename")
-	if _, ok := fileFilter[filename]; !ok {
+	if !fileFilter.Contains(filename) {
 		apiError(ctx, http.StatusBadRequest, nil)
 		return
 	}
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 5dab770d5..38ad593c1 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -30,6 +30,7 @@ import (
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
 	"code.gitea.io/gitea/modules/git"
@@ -947,10 +948,11 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
 		if err != nil {
 			return nil, nil, 0, 0
 		}
-		labelIDMark := base.Int64sToMap(labelIDs)
+		labelIDMark := make(container.Set[int64])
+		labelIDMark.AddMultiple(labelIDs...)
 
 		for i := range labels {
-			if labelIDMark[labels[i].ID] {
+			if labelIDMark.Contains(labels[i].ID) {
 				labels[i].IsChecked = true
 				hasSelected = true
 			}
@@ -1293,9 +1295,9 @@ func ViewIssue(ctx *context.Context) {
 
 	// Metas.
 	// Check labels.
-	labelIDMark := make(map[int64]bool)
-	for i := range issue.Labels {
-		labelIDMark[issue.Labels[i].ID] = true
+	labelIDMark := make(container.Set[int64])
+	for _, label := range issue.Labels {
+		labelIDMark.Add(label.ID)
 	}
 	labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{})
 	if err != nil {
@@ -1317,7 +1319,7 @@ func ViewIssue(ctx *context.Context) {
 
 	hasSelected := false
 	for i := range labels {
-		if labelIDMark[labels[i].ID] {
+		if labelIDMark.Contains(labels[i].ID) {
 			labels[i].IsChecked = true
 			hasSelected = true
 		}
diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go
index baec48bfe..633b8ab1a 100644
--- a/routers/web/repo/lfs.go
+++ b/routers/web/repo/lfs.go
@@ -18,6 +18,7 @@ import (
 	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/git/pipeline"
@@ -176,14 +177,12 @@ func LFSLocks(ctx *context.Context) {
 		return
 	}
 
-	filemap := make(map[string]bool, len(filelist))
-	for _, name := range filelist {
-		filemap[name] = true
-	}
+	fileset := make(container.Set[string], len(filelist))
+	fileset.AddMultiple(filelist...)
 
 	linkable := make([]bool, len(lfsLocks))
 	for i, lock := range lfsLocks {
-		linkable[i] = filemap[lock.Path]
+		linkable[i] = fileset.Contains(lock.Path)
 	}
 	ctx.Data["Linkable"] = linkable
 
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 34305b762..a43840467 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -28,6 +28,7 @@ import (
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/highlight"
@@ -811,16 +812,14 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 		defer cancel()
 	}
 
-	selected := map[string]bool{}
-	for _, pth := range ctx.FormStrings("f[]") {
-		selected[pth] = true
-	}
+	selected := make(container.Set[string])
+	selected.AddMultiple(ctx.FormStrings("f[]")...)
 
 	entries := allEntries
 	if len(selected) > 0 {
 		entries = make(git.Entries, 0, len(selected))
 		for _, entry := range allEntries {
-			if selected[entry.Name()] {
+			if selected.Contains(entry.Name()) {
 				entries = append(entries, entry)
 			}
 		}
diff --git a/services/issue/commit.go b/services/issue/commit.go
index 0d04de81b..c8cfa6cc8 100644
--- a/services/issue/commit.go
+++ b/services/issue/commit.go
@@ -18,6 +18,7 @@ import (
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/references"
 	"code.gitea.io/gitea/modules/repository"
@@ -111,7 +112,7 @@ func UpdateIssuesCommit(doer *user_model.User, repo *repo_model.Repository, comm
 			Action references.XRefAction
 		}
 
-		refMarked := make(map[markKey]bool)
+		refMarked := make(container.Set[markKey])
 		var refRepo *repo_model.Repository
 		var refIssue *issues_model.Issue
 		var err error
@@ -144,10 +145,9 @@ func UpdateIssuesCommit(doer *user_model.User, repo *repo_model.Repository, comm
 			}
 
 			key := markKey{ID: refIssue.ID, Action: ref.Action}
-			if refMarked[key] {
+			if !refMarked.Add(key) {
 				continue
 			}
-			refMarked[key] = true
 
 			// FIXME: this kind of condition is all over the code, it should be consolidated in a single place
 			canclose := perm.IsAdmin() || perm.IsOwner() || perm.CanWriteIssuesOrPulls(refIssue.IsPull) || refIssue.PosterID == doer.ID
diff --git a/services/mailer/mail_comment.go b/services/mailer/mail_comment.go
index 2dab673b4..af07821c2 100644
--- a/services/mailer/mail_comment.go
+++ b/services/mailer/mail_comment.go
@@ -10,6 +10,7 @@ import (
 	activities_model "code.gitea.io/gitea/models/activities"
 	issues_model "code.gitea.io/gitea/models/issues"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -46,8 +47,8 @@ func MailMentionsComment(ctx context.Context, pr *issues_model.PullRequest, c *i
 		return nil
 	}
 
-	visited := make(map[int64]bool, len(mentions)+1)
-	visited[c.Poster.ID] = true
+	visited := make(container.Set[int64], len(mentions)+1)
+	visited.Add(c.Poster.ID)
 	if err = mailIssueCommentBatch(
 		&mailCommentContext{
 			Context:    ctx,
diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go
index ec6ddcf14..15bfa4af4 100644
--- a/services/mailer/mail_issue.go
+++ b/services/mailer/mail_issue.go
@@ -14,6 +14,7 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -89,11 +90,11 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
 		unfiltered = append(ids, unfiltered...)
 	}
 
-	visited := make(map[int64]bool, len(unfiltered)+len(mentions)+1)
+	visited := make(container.Set[int64], len(unfiltered)+len(mentions)+1)
 
 	// Avoid mailing the doer
 	if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn {
-		visited[ctx.Doer.ID] = true
+		visited.Add(ctx.Doer.ID)
 	}
 
 	// =========== Mentions ===========
@@ -106,9 +107,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
 	if err != nil {
 		return fmt.Errorf("GetIssueWatchersIDs(%d): %v", ctx.Issue.ID, err)
 	}
-	for _, i := range ids {
-		visited[i] = true
-	}
+	visited.AddMultiple(ids...)
 
 	unfilteredUsers, err := user_model.GetMaileableUsersByIDs(unfiltered, false)
 	if err != nil {
@@ -121,7 +120,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
 	return nil
 }
 
-func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, visited map[int64]bool, fromMention bool) error {
+func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, visited container.Set[int64], fromMention bool) error {
 	checkUnit := unit.TypeIssues
 	if ctx.Issue.IsPull {
 		checkUnit = unit.TypePullRequests
@@ -142,13 +141,10 @@ func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, vi
 		}
 
 		// if we have already visited this user we exclude them
-		if _, ok := visited[user.ID]; ok {
+		if !visited.Add(user.ID) {
 			continue
 		}
 
-		// now mark them as visited
-		visited[user.ID] = true
-
 		// test if this user is allowed to see the issue/pull
 		if !access_model.CheckRepoUnitUser(ctx, ctx.Issue.Repo, user, checkUnit) {
 			continue
diff --git a/services/pull/patch.go b/services/pull/patch.go
index 32895b2e7..dafd57706 100644
--- a/services/pull/patch.go
+++ b/services/pull/patch.go
@@ -17,6 +17,7 @@ import (
 	"code.gitea.io/gitea/models"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/unit"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/log"
@@ -409,7 +410,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
 				const appliedPatchPrefix = "Applied patch to '"
 				const withConflicts = "' with conflicts."
 
-				conflictMap := map[string]bool{}
+				conflicts := make(container.Set[string])
 
 				// Now scan the output from the command
 				scanner := bufio.NewScanner(stderrReader)
@@ -418,7 +419,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
 					if strings.HasPrefix(line, prefix) {
 						conflict = true
 						filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0])
-						conflictMap[filepath] = true
+						conflicts.Add(filepath)
 					} else if is3way && line == threewayFailed {
 						conflict = true
 					} else if strings.HasPrefix(line, errorPrefix) {
@@ -427,7 +428,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
 							if strings.HasSuffix(line, suffix) {
 								filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix))
 								if filepath != "" {
-									conflictMap[filepath] = true
+									conflicts.Add(filepath)
 								}
 								break
 							}
@@ -436,18 +437,18 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
 						conflict = true
 						filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix)
 						if filepath != "" {
-							conflictMap[filepath] = true
+							conflicts.Add(filepath)
 						}
 					}
 					// only list 10 conflicted files
-					if len(conflictMap) >= 10 {
+					if len(conflicts) >= 10 {
 						break
 					}
 				}
 
-				if len(conflictMap) > 0 {
-					pr.ConflictedFiles = make([]string, 0, len(conflictMap))
-					for key := range conflictMap {
+				if len(conflicts) > 0 {
+					pr.ConflictedFiles = make([]string, 0, len(conflicts))
+					for key := range conflicts {
 						pr.ConflictedFiles = append(pr.ConflictedFiles, key)
 					}
 				}
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 103fdc340..9de7cb5d4 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -20,6 +20,7 @@ import (
 	issues_model "code.gitea.io/gitea/models/issues"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/json"
@@ -640,7 +641,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
 
 	posterSig := pr.Issue.Poster.NewGitSig().String()
 
-	authorsMap := map[string]bool{}
+	uniqueAuthors := make(container.Set[string])
 	authors := make([]string, 0, len(commits))
 	stringBuilder := strings.Builder{}
 
@@ -687,9 +688,8 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
 		}
 
 		authorString := commit.Author.String()
-		if !authorsMap[authorString] && authorString != posterSig {
+		if uniqueAuthors.Add(authorString) && authorString != posterSig {
 			authors = append(authors, authorString)
-			authorsMap[authorString] = true
 		}
 	}
 
@@ -709,9 +709,8 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
 			}
 			for _, commit := range commits {
 				authorString := commit.Author.String()
-				if !authorsMap[authorString] && authorString != posterSig {
+				if uniqueAuthors.Add(authorString) && authorString != posterSig {
 					authors = append(authors, authorString)
-					authorsMap[authorString] = true
 				}
 			}
 			skip += limit
diff --git a/services/release/release.go b/services/release/release.go
index ae610b0e3..187ebeb48 100644
--- a/services/release/release.go
+++ b/services/release/release.go
@@ -15,6 +15,7 @@ import (
 	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/notification"
@@ -209,7 +210,7 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
 		return fmt.Errorf("AddReleaseAttachments: %v", err)
 	}
 
-	deletedUUIDsMap := make(map[string]bool)
+	deletedUUIDs := make(container.Set[string])
 	if len(delAttachmentUUIDs) > 0 {
 		// Check attachments
 		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, delAttachmentUUIDs)
@@ -220,7 +221,7 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
 			if attach.ReleaseID != rel.ID {
 				return errors.New("delete attachement of release permission denied")
 			}
-			deletedUUIDsMap[attach.UUID] = true
+			deletedUUIDs.Add(attach.UUID)
 		}
 
 		if _, err := repo_model.DeleteAttachments(ctx, attachments, false); err != nil {
@@ -245,7 +246,7 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
 		}
 
 		for uuid, newName := range editAttachments {
-			if !deletedUUIDsMap[uuid] {
+			if !deletedUUIDs.Contains(uuid) {
 				if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{
 					UUID: uuid,
 					Name: newName,
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index 74876d8e7..9e04c1597 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/notification"
@@ -257,12 +258,12 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad
 	if len(repos) == len(repoNamesToCheck) {
 		return nil
 	}
-	repoNames := make(map[string]bool, len(repos))
+	repoNames := make(container.Set[string], len(repos))
 	for _, repo := range repos {
-		repoNames[repo.LowerName] = true
+		repoNames.Add(repo.LowerName)
 	}
 	for _, repoName := range repoNamesToCheck {
-		if _, ok := repoNames[repoName]; !ok {
+		if !repoNames.Contains(repoName) {
 			unadopted.add(filepath.Join(userName, repoName))
 		}
 	}