mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:06:18 +01:00 
			
		
		
		
	Add generic set type (#21408)
This PR adds a generic set type to get rid of maps used as sets. Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -18,13 +18,11 @@ import ( | |||||||
| type ActionList []*Action | type ActionList []*Action | ||||||
|  |  | ||||||
| func (actions ActionList) getUserIDs() []int64 { | func (actions ActionList) getUserIDs() []int64 { | ||||||
| 	userIDs := make(map[int64]struct{}, len(actions)) | 	userIDs := make(container.Set[int64], len(actions)) | ||||||
| 	for _, action := range actions { | 	for _, action := range actions { | ||||||
| 		if _, ok := userIDs[action.ActUserID]; !ok { | 		userIDs.Add(action.ActUserID) | ||||||
| 			userIDs[action.ActUserID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(userIDs) | 	return userIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) { | 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 { | func (actions ActionList) getRepoIDs() []int64 { | ||||||
| 	repoIDs := make(map[int64]struct{}, len(actions)) | 	repoIDs := make(container.Set[int64], len(actions)) | ||||||
| 	for _, action := range actions { | 	for _, action := range actions { | ||||||
| 		if _, ok := repoIDs[action.RepoID]; !ok { | 		repoIDs.Add(action.RepoID) | ||||||
| 			repoIDs[action.RepoID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(repoIDs) | 	return repoIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (actions ActionList) loadRepositories(ctx context.Context) error { | func (actions ActionList) loadRepositories(ctx context.Context) error { | ||||||
|   | |||||||
| @@ -200,7 +200,7 @@ func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID, | |||||||
|  |  | ||||||
| func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { | func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { | ||||||
| 	// init | 	// init | ||||||
| 	var toNotify map[int64]struct{} | 	var toNotify container.Set[int64] | ||||||
| 	notifications, err := getNotificationsByIssueID(ctx, issueID) | 	notifications, err := getNotificationsByIssueID(ctx, issueID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -212,33 +212,27 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if receiverID > 0 { | 	if receiverID > 0 { | ||||||
| 		toNotify = make(map[int64]struct{}, 1) | 		toNotify = make(container.Set[int64], 1) | ||||||
| 		toNotify[receiverID] = struct{}{} | 		toNotify.Add(receiverID) | ||||||
| 	} else { | 	} else { | ||||||
| 		toNotify = make(map[int64]struct{}, 32) | 		toNotify = make(container.Set[int64], 32) | ||||||
| 		issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true) | 		issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		for _, id := range issueWatches { | 		toNotify.AddMultiple(issueWatches...) | ||||||
| 			toNotify[id] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 		if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { | 		if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { | ||||||
| 			repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) | 			repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 			for _, id := range repoWatches { | 			toNotify.AddMultiple(repoWatches...) | ||||||
| 				toNotify[id] = struct{}{} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 		issueParticipants, err := issue.GetParticipantIDsByIssue(ctx) | 		issueParticipants, err := issue.GetParticipantIDsByIssue(ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		for _, id := range issueParticipants { | 		toNotify.AddMultiple(issueParticipants...) | ||||||
| 			toNotify[id] = struct{}{} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// dont notify user who cause notification | 		// dont notify user who cause notification | ||||||
| 		delete(toNotify, notificationAuthorID) | 		delete(toNotify, notificationAuthorID) | ||||||
| @@ -248,7 +242,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		for _, id := range issueUnWatches { | 		for _, id := range issueUnWatches { | ||||||
| 			delete(toNotify, id) | 			toNotify.Remove(id) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -499,16 +493,14 @@ func (nl NotificationList) LoadAttributes() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (nl NotificationList) getPendingRepoIDs() []int64 { | func (nl NotificationList) getPendingRepoIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(nl)) | 	ids := make(container.Set[int64], len(nl)) | ||||||
| 	for _, notification := range nl { | 	for _, notification := range nl { | ||||||
| 		if notification.Repository != nil { | 		if notification.Repository != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if _, ok := ids[notification.RepoID]; !ok { | 		ids.Add(notification.RepoID) | ||||||
| 			ids[notification.RepoID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| // LoadRepos loads repositories from database | // LoadRepos loads repositories from database | ||||||
| @@ -575,16 +567,14 @@ func (nl NotificationList) LoadRepos() (repo_model.RepositoryList, []int, error) | |||||||
| } | } | ||||||
|  |  | ||||||
| func (nl NotificationList) getPendingIssueIDs() []int64 { | func (nl NotificationList) getPendingIssueIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(nl)) | 	ids := make(container.Set[int64], len(nl)) | ||||||
| 	for _, notification := range nl { | 	for _, notification := range nl { | ||||||
| 		if notification.Issue != nil { | 		if notification.Issue != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if _, ok := ids[notification.IssueID]; !ok { | 		ids.Add(notification.IssueID) | ||||||
| 			ids[notification.IssueID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| // LoadIssues loads issues from database | // LoadIssues loads issues from database | ||||||
| @@ -661,16 +651,14 @@ func (nl NotificationList) Without(failures []int) NotificationList { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (nl NotificationList) getPendingCommentIDs() []int64 { | func (nl NotificationList) getPendingCommentIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(nl)) | 	ids := make(container.Set[int64], len(nl)) | ||||||
| 	for _, notification := range nl { | 	for _, notification := range nl { | ||||||
| 		if notification.CommentID == 0 || notification.Comment != nil { | 		if notification.CommentID == 0 || notification.Comment != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if _, ok := ids[notification.CommentID]; !ok { | 		ids.Add(notification.CommentID) | ||||||
| 			ids[notification.CommentID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| // LoadComments loads comments from database | // LoadComments loads comments from database | ||||||
|   | |||||||
| @@ -17,13 +17,11 @@ import ( | |||||||
| type CommentList []*Comment | type CommentList []*Comment | ||||||
|  |  | ||||||
| func (comments CommentList) getPosterIDs() []int64 { | func (comments CommentList) getPosterIDs() []int64 { | ||||||
| 	posterIDs := make(map[int64]struct{}, len(comments)) | 	posterIDs := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if _, ok := posterIDs[comment.PosterID]; !ok { | 		posterIDs.Add(comment.PosterID) | ||||||
| 			posterIDs[comment.PosterID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(posterIDs) | 	return posterIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadPosters(ctx context.Context) error { | func (comments CommentList) loadPosters(ctx context.Context) error { | ||||||
| @@ -70,13 +68,11 @@ func (comments CommentList) getCommentIDs() []int64 { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) getLabelIDs() []int64 { | func (comments CommentList) getLabelIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if _, ok := ids[comment.LabelID]; !ok { | 		ids.Add(comment.LabelID) | ||||||
| 			ids[comment.LabelID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadLabels(ctx context.Context) error { //nolint | 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 { | func (comments CommentList) getMilestoneIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if _, ok := ids[comment.MilestoneID]; !ok { | 		ids.Add(comment.MilestoneID) | ||||||
| 			ids[comment.MilestoneID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadMilestones(ctx context.Context) error { | 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 { | func (comments CommentList) getOldMilestoneIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if _, ok := ids[comment.OldMilestoneID]; !ok { | 		ids.Add(comment.OldMilestoneID) | ||||||
| 			ids[comment.OldMilestoneID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadOldMilestones(ctx context.Context) error { | 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 { | func (comments CommentList) getAssigneeIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if _, ok := ids[comment.AssigneeID]; !ok { | 		ids.Add(comment.AssigneeID) | ||||||
| 			ids[comment.AssigneeID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadAssignees(ctx context.Context) error { | 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 | // getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded | ||||||
| func (comments CommentList) getIssueIDs() []int64 { | func (comments CommentList) getIssueIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if comment.Issue != nil { | 		if comment.Issue != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if _, ok := ids[comment.IssueID]; !ok { | 		ids.Add(comment.IssueID) | ||||||
| 			ids[comment.IssueID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| // Issues returns all the issues of comments | // Issues returns all the issues of comments | ||||||
| @@ -334,16 +322,14 @@ func (comments CommentList) loadIssues(ctx context.Context) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) getDependentIssueIDs() []int64 { | func (comments CommentList) getDependentIssueIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if comment.DependentIssue != nil { | 		if comment.DependentIssue != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if _, ok := ids[comment.DependentIssueID]; !ok { | 		ids.Add(comment.DependentIssueID) | ||||||
| 			ids[comment.DependentIssueID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadDependentIssues(ctx context.Context) error { | 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 { | func (comments CommentList) getReviewIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(comments)) | 	ids := make(container.Set[int64], len(comments)) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		if _, ok := ids[comment.ReviewID]; !ok { | 		ids.Add(comment.ReviewID) | ||||||
| 			ids[comment.ReviewID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (comments CommentList) loadReviews(ctx context.Context) error { //nolint | func (comments CommentList) loadReviews(ctx context.Context) error { //nolint | ||||||
|   | |||||||
| @@ -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 | // get the repo IDs to be loaded later, these IDs are for issue.Repo and issue.PullRequest.HeadRepo | ||||||
| func (issues IssueList) getRepoIDs() []int64 { | func (issues IssueList) getRepoIDs() []int64 { | ||||||
| 	repoIDs := make(map[int64]struct{}, len(issues)) | 	repoIDs := make(container.Set[int64], len(issues)) | ||||||
| 	for _, issue := range issues { | 	for _, issue := range issues { | ||||||
| 		if issue.Repo == nil { | 		if issue.Repo == nil { | ||||||
| 			repoIDs[issue.RepoID] = struct{}{} | 			repoIDs.Add(issue.RepoID) | ||||||
| 		} | 		} | ||||||
| 		if issue.PullRequest != nil && issue.PullRequest.HeadRepo == nil { | 		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) { | 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 { | func (issues IssueList) getPosterIDs() []int64 { | ||||||
| 	posterIDs := make(map[int64]struct{}, len(issues)) | 	posterIDs := make(container.Set[int64], len(issues)) | ||||||
| 	for _, issue := range issues { | 	for _, issue := range issues { | ||||||
| 		if _, ok := posterIDs[issue.PosterID]; !ok { | 		posterIDs.Add(issue.PosterID) | ||||||
| 			posterIDs[issue.PosterID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(posterIDs) | 	return posterIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (issues IssueList) loadPosters(ctx context.Context) error { | 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 { | func (issues IssueList) getMilestoneIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(issues)) | 	ids := make(container.Set[int64], len(issues)) | ||||||
| 	for _, issue := range issues { | 	for _, issue := range issues { | ||||||
| 		if _, ok := ids[issue.MilestoneID]; !ok { | 		ids.Add(issue.MilestoneID) | ||||||
| 			ids[issue.MilestoneID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (issues IssueList) loadMilestones(ctx context.Context) error { | 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 { | func (issues IssueList) getProjectIDs() []int64 { | ||||||
| 	ids := make(map[int64]struct{}, len(issues)) | 	ids := make(container.Set[int64], len(issues)) | ||||||
| 	for _, issue := range issues { | 	for _, issue := range issues { | ||||||
| 		projectID := issue.ProjectID() | 		ids.Add(issue.ProjectID()) | ||||||
| 		if _, ok := ids[projectID]; !ok { |  | ||||||
| 			ids[projectID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(ids) | 	return ids.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (issues IssueList) loadProjects(ctx context.Context) error { | func (issues IssueList) loadProjects(ctx context.Context) error { | ||||||
|   | |||||||
| @@ -211,7 +211,7 @@ type ReactionOptions struct { | |||||||
|  |  | ||||||
| // CreateReaction creates reaction for issue or comment. | // CreateReaction creates reaction for issue or comment. | ||||||
| func CreateReaction(opts *ReactionOptions) (*Reaction, error) { | func CreateReaction(opts *ReactionOptions) (*Reaction, error) { | ||||||
| 	if !setting.UI.ReactionsMap[opts.Type] { | 	if !setting.UI.ReactionsLookup.Contains(opts.Type) { | ||||||
| 		return nil, ErrForbiddenIssueReaction{opts.Type} | 		return nil, ErrForbiddenIssueReaction{opts.Type} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -316,16 +316,14 @@ func (list ReactionList) GroupByType() map[string]ReactionList { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (list ReactionList) getUserIDs() []int64 { | func (list ReactionList) getUserIDs() []int64 { | ||||||
| 	userIDs := make(map[int64]struct{}, len(list)) | 	userIDs := make(container.Set[int64], len(list)) | ||||||
| 	for _, reaction := range list { | 	for _, reaction := range list { | ||||||
| 		if reaction.OriginalAuthor != "" { | 		if reaction.OriginalAuthor != "" { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if _, ok := userIDs[reaction.UserID]; !ok { | 		userIDs.Add(reaction.UserID) | ||||||
| 			userIDs[reaction.UserID] = struct{}{} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return container.KeysInt64(userIDs) | 	return userIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func valuesUser(m map[int64]*user_model.User) []*user_model.User { | func valuesUser(m map[int64]*user_model.User) []*user_model.User { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"code.gitea.io/gitea/modules/structs" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -99,9 +100,9 @@ func InsertIssueComments(comments []*issues_model.Comment) error { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	issueIDs := make(map[int64]bool) | 	issueIDs := make(container.Set[int64]) | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
| 		issueIDs[comment.IssueID] = true | 		issueIDs.Add(comment.IssueID) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx, committer, err := db.TxContext() | 	ctx, committer, err := db.TxContext() | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"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) | 	log.Info("%d User Avatar(s) to migrate ...", count) | ||||||
|  |  | ||||||
| 	deleteList := make(map[string]struct{}) | 	deleteList := make(container.Set[string]) | ||||||
| 	start := 0 | 	start := 0 | ||||||
| 	migrated := 0 | 	migrated := 0 | ||||||
| 	for { | 	for { | ||||||
| @@ -86,7 +87,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error { | |||||||
| 				return fmt.Errorf("[user: %s] user table update: %v", user.LowerName, err) | 				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++ | 			migrated++ | ||||||
| 			select { | 			select { | ||||||
| 			case <-ticker.C: | 			case <-ticker.C: | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	"code.gitea.io/gitea/models/packages" | 	"code.gitea.io/gitea/models/packages" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	conan_module "code.gitea.io/gitea/modules/packages/conan" | 	conan_module "code.gitea.io/gitea/modules/packages/conan" | ||||||
|  |  | ||||||
| 	"xorm.io/builder" | 	"xorm.io/builder" | ||||||
| @@ -88,7 +89,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	unique := make(map[string]bool) | 	unique := make(container.Set[string]) | ||||||
| 	for _, info := range results { | 	for _, info := range results { | ||||||
| 		recipe := fmt.Sprintf("%s/%s", info.Name, info.Version) | 		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)) | 	recipes := make([]string, 0, len(unique)) | ||||||
|   | |||||||
| @@ -68,10 +68,10 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	set := make(map[int64]struct{}) | 	set := make(container.Set[int64]) | ||||||
| 	repoIDs := make([]int64, len(repos)) | 	repoIDs := make([]int64, len(repos)) | ||||||
| 	for i := range repos { | 	for i := range repos { | ||||||
| 		set[repos[i].OwnerID] = struct{}{} | 		set.Add(repos[i].OwnerID) | ||||||
| 		repoIDs[i] = repos[i].ID | 		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)) | 	users := make(map[int64]*user_model.User, len(set)) | ||||||
| 	if err := db.GetEngine(ctx). | 	if err := db.GetEngine(ctx). | ||||||
| 		Where("id > 0"). | 		Where("id > 0"). | ||||||
| 		In("id", container.KeysInt64(set)). | 		In("id", set.Values()). | ||||||
| 		Find(&users); err != nil { | 		Find(&users); err != nil { | ||||||
| 		return fmt.Errorf("find users: %v", err) | 		return fmt.Errorf("find users: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
|  |  | ||||||
| 	"xorm.io/builder" | 	"xorm.io/builder" | ||||||
| @@ -62,7 +63,7 @@ func ValidateTopic(topic string) bool { | |||||||
| // SanitizeAndValidateTopics sanitizes and checks an array or topics | // SanitizeAndValidateTopics sanitizes and checks an array or topics | ||||||
| func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []string) { | func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []string) { | ||||||
| 	validTopics = make([]string, 0) | 	validTopics = make([]string, 0) | ||||||
| 	mValidTopics := make(map[string]struct{}) | 	mValidTopics := make(container.Set[string]) | ||||||
| 	invalidTopics = make([]string, 0) | 	invalidTopics = make([]string, 0) | ||||||
|  |  | ||||||
| 	for _, topic := range topics { | 	for _, topic := range topics { | ||||||
| @@ -72,12 +73,12 @@ func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []st | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		// ignore same topic twice | 		// ignore same topic twice | ||||||
| 		if _, ok := mValidTopics[topic]; ok { | 		if mValidTopics.Contains(topic) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if ValidateTopic(topic) { | 		if ValidateTopic(topic) { | ||||||
| 			validTopics = append(validTopics, topic) | 			validTopics = append(validTopics, topic) | ||||||
| 			mValidTopics[topic] = struct{}{} | 			mValidTopics.Add(topic) | ||||||
| 		} else { | 		} else { | ||||||
| 			invalidTopics = append(invalidTopics, topic) | 			invalidTopics = append(invalidTopics, topic) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	"code.gitea.io/gitea/models/perm" | 	"code.gitea.io/gitea/models/perm" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  |  | ||||||
| 	"xorm.io/builder" | 	"xorm.io/builder" | ||||||
| @@ -83,37 +84,19 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	uidMap := map[int64]bool{} | 	uniqueUserIDs := make(container.Set[int64]) | ||||||
| 	i := 0 | 	uniqueUserIDs.AddMultiple(userIDs...) | ||||||
| 	for _, uid := range userIDs { | 	uniqueUserIDs.AddMultiple(additionalUserIDs...) | ||||||
| 		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] |  | ||||||
|  |  | ||||||
| 	// Leave a seat for owner itself to append later, but if owner is an organization | 	// 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. | 	// 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 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 | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] { | 	if !repo.Owner.IsOrganization() && !uniqueUserIDs.Contains(repo.OwnerID) { | ||||||
| 		users = append(users, repo.Owner) | 		users = append(users, repo.Owner) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -241,15 +241,6 @@ func Int64sToStrings(ints []int64) []string { | |||||||
| 	return strs | 	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 | // Int64sContains returns if a int64 in a slice of int64 | ||||||
| func Int64sContains(intsSlice []int64, a int64) bool { | func Int64sContains(intsSlice []int64, a int64) bool { | ||||||
| 	for _, c := range intsSlice { | 	for _, c := range intsSlice { | ||||||
|   | |||||||
| @@ -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) { | 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{6, 44324, 4324, 32, 1, 2323}, 1)) | ||||||
| 	assert.True(t, Int64sContains([]int64{2323}, 2323)) | 	assert.True(t, Int64sContains([]int64{2323}, 2323)) | ||||||
| 	assert.False(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232)) | 	assert.False(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232)) | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
							
								
								
									
										57
									
								
								modules/container/set.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								modules/container/set.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								modules/container/set_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								modules/container/set_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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")) | ||||||
|  | } | ||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	asymkey_model "code.gitea.io/gitea/models/asymkey" | 	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/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
| @@ -40,7 +41,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e | |||||||
| 	} | 	} | ||||||
| 	defer f.Close() | 	defer f.Close() | ||||||
|  |  | ||||||
| 	linesInAuthorizedKeys := map[string]bool{} | 	linesInAuthorizedKeys := make(container.Set[string]) | ||||||
|  |  | ||||||
| 	scanner := bufio.NewScanner(f) | 	scanner := bufio.NewScanner(f) | ||||||
| 	for scanner.Scan() { | 	for scanner.Scan() { | ||||||
| @@ -48,7 +49,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e | |||||||
| 		if strings.HasPrefix(line, tplCommentPrefix) { | 		if strings.HasPrefix(line, tplCommentPrefix) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		linesInAuthorizedKeys[line] = true | 		linesInAuthorizedKeys.Add(line) | ||||||
| 	} | 	} | ||||||
| 	f.Close() | 	f.Close() | ||||||
|  |  | ||||||
| @@ -64,7 +65,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e | |||||||
| 		if strings.HasPrefix(line, tplCommentPrefix) { | 		if strings.HasPrefix(line, tplCommentPrefix) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if ok := linesInAuthorizedKeys[line]; ok { | 		if linesInAuthorizedKeys.Contains(line) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if !autofix { | 		if !autofix { | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ import ( | |||||||
| 	"sort" | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
|  |  | ||||||
| 	"github.com/djherbis/buffer" | 	"github.com/djherbis/buffer" | ||||||
| 	"github.com/djherbis/nio/v3" | 	"github.com/djherbis/nio/v3" | ||||||
| ) | ) | ||||||
| @@ -339,7 +341,7 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st | |||||||
| 	lastEmptyParent := head.ID.String() | 	lastEmptyParent := head.ID.String() | ||||||
| 	commitSinceLastEmptyParent := uint64(0) | 	commitSinceLastEmptyParent := uint64(0) | ||||||
| 	commitSinceNextRestart := uint64(0) | 	commitSinceNextRestart := uint64(0) | ||||||
| 	parentRemaining := map[string]bool{} | 	parentRemaining := make(container.Set[string]) | ||||||
|  |  | ||||||
| 	changed := make([]bool, len(paths)) | 	changed := make([]bool, len(paths)) | ||||||
|  |  | ||||||
| @@ -365,7 +367,7 @@ heaploop: | |||||||
| 		if current == nil { | 		if current == nil { | ||||||
| 			break heaploop | 			break heaploop | ||||||
| 		} | 		} | ||||||
| 		delete(parentRemaining, current.CommitID) | 		parentRemaining.Remove(current.CommitID) | ||||||
| 		if current.Paths != nil { | 		if current.Paths != nil { | ||||||
| 			for i, found := range current.Paths { | 			for i, found := range current.Paths { | ||||||
| 				if !found { | 				if !found { | ||||||
| @@ -410,14 +412,12 @@ heaploop: | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				g = NewLogNameStatusRepoParser(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...) | 				g = NewLogNameStatusRepoParser(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...) | ||||||
| 				parentRemaining = map[string]bool{} | 				parentRemaining = make(container.Set[string]) | ||||||
| 				nextRestart = (remaining * 3) / 4 | 				nextRestart = (remaining * 3) / 4 | ||||||
| 				continue heaploop | 				continue heaploop | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		for _, parent := range current.ParentIDs { | 		parentRemaining.AddMultiple(current.ParentIDs...) | ||||||
| 			parentRemaining[parent] = true |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	g.Close() | 	g.Close() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ import ( | |||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CodeActivityStats represents git statistics data | // CodeActivityStats represents git statistics data | ||||||
| @@ -80,7 +82,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) | |||||||
| 			stats.Additions = 0 | 			stats.Additions = 0 | ||||||
| 			stats.Deletions = 0 | 			stats.Deletions = 0 | ||||||
| 			authors := make(map[string]*CodeActivityAuthor) | 			authors := make(map[string]*CodeActivityAuthor) | ||||||
| 			files := make(map[string]bool) | 			files := make(container.Set[string]) | ||||||
| 			var author string | 			var author string | ||||||
| 			p := 0 | 			p := 0 | ||||||
| 			for scanner.Scan() { | 			for scanner.Scan() { | ||||||
| @@ -119,9 +121,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) | |||||||
| 								stats.Deletions += c | 								stats.Deletions += c | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 						if _, ok := files[parts[2]]; !ok { | 						files.Add(parts[2]) | ||||||
| 							files[parts[2]] = true |  | ||||||
| 						} |  | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  |  | ||||||
| 	"gitea.com/go-chi/binding" | 	"gitea.com/go-chi/binding" | ||||||
| @@ -43,7 +44,7 @@ func validateYaml(template *api.IssueTemplate) error { | |||||||
| 	if len(template.Fields) == 0 { | 	if len(template.Fields) == 0 { | ||||||
| 		return fmt.Errorf("'body' is required") | 		return fmt.Errorf("'body' is required") | ||||||
| 	} | 	} | ||||||
| 	ids := map[string]struct{}{} | 	ids := make(container.Set[string]) | ||||||
| 	for idx, field := range template.Fields { | 	for idx, field := range template.Fields { | ||||||
| 		if err := validateID(field, idx, ids); err != nil { | 		if err := validateID(field, idx, ids); err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -125,7 +126,7 @@ func validateRequired(field *api.IssueFormField, idx int) error { | |||||||
| 	return validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required") | 	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 { | 	if field.Type == api.IssueFormFieldTypeMarkdown { | ||||||
| 		// The ID is not required for a markdown field | 		// The ID is not required for a markdown field | ||||||
| 		return nil | 		return nil | ||||||
| @@ -139,10 +140,9 @@ func validateID(field *api.IssueFormField, idx int, ids map[string]struct{}) err | |||||||
| 	if binding.AlphaDashPattern.MatchString(field.ID) { | 	if binding.AlphaDashPattern.MatchString(field.ID) { | ||||||
| 		return position.Errorf("'id' should contain only alphanumeric, '-' and '_'") | 		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") | 		return position.Errorf("'id' should be unique") | ||||||
| 	} | 	} | ||||||
| 	ids[field.ID] = struct{}{} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/markup" | 	"code.gitea.io/gitea/modules/markup" | ||||||
| 	"code.gitea.io/gitea/modules/markup/common" | 	"code.gitea.io/gitea/modules/markup/common" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"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 { | type prefixedIDs struct { | ||||||
| 	values map[string]bool | 	values container.Set[string] | ||||||
| } | } | ||||||
|  |  | ||||||
| // Generate generates a new element id. | // 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-")) { | 	if !bytes.HasPrefix(result, []byte("user-content-")) { | ||||||
| 		result = append([]byte("user-content-"), result...) | 		result = append([]byte("user-content-"), result...) | ||||||
| 	} | 	} | ||||||
| 	if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok { | 	if p.values.Add(util.BytesToReadOnlyString(result)) { | ||||||
| 		p.values[util.BytesToReadOnlyString(result)] = true |  | ||||||
| 		return result | 		return result | ||||||
| 	} | 	} | ||||||
| 	for i := 1; ; i++ { | 	for i := 1; ; i++ { | ||||||
| 		newResult := fmt.Sprintf("%s-%d", result, i) | 		newResult := fmt.Sprintf("%s-%d", result, i) | ||||||
| 		if _, ok := p.values[newResult]; !ok { | 		if p.values.Add(newResult) { | ||||||
| 			p.values[newResult] = true |  | ||||||
| 			return []byte(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. | // Put puts a given element id to the used ids table. | ||||||
| func (p *prefixedIDs) Put(value []byte) { | func (p *prefixedIDs) Put(value []byte) { | ||||||
| 	p.values[util.BytesToReadOnlyString(value)] = true | 	p.values.Add(util.BytesToReadOnlyString(value)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func newPrefixedIDs() *prefixedIDs { | func newPrefixedIDs() *prefixedIDs { | ||||||
| 	return &prefixedIDs{ | 	return &prefixedIDs{ | ||||||
| 		values: map[string]bool{}, | 		values: make(container.Set[string]), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/graceful" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/notification/base" | 	"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) | 		log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	toNotify := make(map[int64]struct{}, 32) | 	toNotify := make(container.Set[int64], 32) | ||||||
| 	repoWatchers, err := repo_model.GetRepoWatchersIDs(db.DefaultContext, pr.Issue.RepoID) | 	repoWatchers, err := repo_model.GetRepoWatchersIDs(db.DefaultContext, pr.Issue.RepoID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error("GetRepoWatchersIDs: %v", err) | 		log.Error("GetRepoWatchersIDs: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	for _, id := range repoWatchers { | 	for _, id := range repoWatchers { | ||||||
| 		toNotify[id] = struct{}{} | 		toNotify.Add(id) | ||||||
| 	} | 	} | ||||||
| 	issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(pr.IssueID) | 	issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(pr.IssueID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -138,11 +139,11 @@ func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	for _, id := range issueParticipants { | 	for _, id := range issueParticipants { | ||||||
| 		toNotify[id] = struct{}{} | 		toNotify.Add(id) | ||||||
| 	} | 	} | ||||||
| 	delete(toNotify, pr.Issue.PosterID) | 	delete(toNotify, pr.Issue.PosterID) | ||||||
| 	for _, mention := range mentions { | 	for _, mention := range mentions { | ||||||
| 		toNotify[mention.ID] = struct{}{} | 		toNotify.Add(mention.ID) | ||||||
| 	} | 	} | ||||||
| 	for receiverID := range toNotify { | 	for receiverID := range toNotify { | ||||||
| 		_ = ns.issueQueue.Push(issueNotificationOpts{ | 		_ = ns.issueQueue.Push(issueNotificationOpts{ | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/httpcache" | 	"code.gitea.io/gitea/modules/httpcache" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"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 | // 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, ";") | 	parts := strings.Split(val, ";") | ||||||
| 	types := make(map[string]bool) | 	types := make(container.Set[string]) | ||||||
| 	for _, v := range strings.Split(parts[0], ",") { | 	for _, v := range strings.Split(parts[0], ",") { | ||||||
| 		types[strings.TrimSpace(v)] = true | 		types.Add(strings.TrimSpace(v)) | ||||||
| 	} | 	} | ||||||
| 	return types | 	return types | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,28 +7,23 @@ package public | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestParseAcceptEncoding(t *testing.T) { | func TestParseAcceptEncoding(t *testing.T) { | ||||||
| 	kases := []struct { | 	kases := []struct { | ||||||
| 		Header   string | 		Header   string | ||||||
| 		Expected map[string]bool | 		Expected container.Set[string] | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			Header: "deflate, gzip;q=1.0, *;q=0.5", | 			Header:   "deflate, gzip;q=1.0, *;q=0.5", | ||||||
| 			Expected: map[string]bool{ | 			Expected: container.SetOf("deflate", "gzip"), | ||||||
| 				"deflate": true, |  | ||||||
| 				"gzip":    true, |  | ||||||
| 			}, |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Header: " gzip, deflate, br", | 			Header:   " gzip, deflate, br", | ||||||
| 			Expected: map[string]bool{ | 			Expected: container.SetOf("deflate", "gzip", "br"), | ||||||
| 				"deflate": true, |  | ||||||
| 				"gzip":    true, |  | ||||||
| 				"br":      true, |  | ||||||
| 			}, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ func AssetIsDir(name string) (bool, error) { | |||||||
| // serveContent serve http content | // serveContent serve http content | ||||||
| func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { | func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { | ||||||
| 	encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) | 	encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) | ||||||
| 	if encodings["gzip"] { | 	if encodings.Contains("gzip") { | ||||||
| 		if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok { | 		if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok { | ||||||
| 			rdGzip := bytes.NewReader(cf.GzipBytes()) | 			rdGzip := bytes.NewReader(cf.GzipBytes()) | ||||||
| 			// all static files are managed by Gitea, so we can make sure every file has the correct ext name | 			// all static files are managed by Gitea, so we can make sure every file has the correct ext name | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| ) | ) | ||||||
| @@ -33,7 +34,7 @@ type ChannelUniqueQueueConfiguration ChannelQueueConfiguration | |||||||
| type ChannelUniqueQueue struct { | type ChannelUniqueQueue struct { | ||||||
| 	*WorkerPool | 	*WorkerPool | ||||||
| 	lock               sync.Mutex | 	lock               sync.Mutex | ||||||
| 	table              map[string]bool | 	table              container.Set[string] | ||||||
| 	shutdownCtx        context.Context | 	shutdownCtx        context.Context | ||||||
| 	shutdownCtxCancel  context.CancelFunc | 	shutdownCtxCancel  context.CancelFunc | ||||||
| 	terminateCtx       context.Context | 	terminateCtx       context.Context | ||||||
| @@ -58,7 +59,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue | |||||||
| 	shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx) | 	shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx) | ||||||
|  |  | ||||||
| 	queue := &ChannelUniqueQueue{ | 	queue := &ChannelUniqueQueue{ | ||||||
| 		table:              map[string]bool{}, | 		table:              make(container.Set[string]), | ||||||
| 		shutdownCtx:        shutdownCtx, | 		shutdownCtx:        shutdownCtx, | ||||||
| 		shutdownCtxCancel:  shutdownCtxCancel, | 		shutdownCtxCancel:  shutdownCtxCancel, | ||||||
| 		terminateCtx:       terminateCtx, | 		terminateCtx:       terminateCtx, | ||||||
| @@ -73,7 +74,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue | |||||||
| 			bs, _ := json.Marshal(datum) | 			bs, _ := json.Marshal(datum) | ||||||
|  |  | ||||||
| 			queue.lock.Lock() | 			queue.lock.Lock() | ||||||
| 			delete(queue.table, string(bs)) | 			queue.table.Remove(string(bs)) | ||||||
| 			queue.lock.Unlock() | 			queue.lock.Unlock() | ||||||
|  |  | ||||||
| 			if u := handle(datum); u != nil { | 			if u := handle(datum); u != nil { | ||||||
| @@ -127,16 +128,15 @@ func (q *ChannelUniqueQueue) PushFunc(data Data, fn func() error) error { | |||||||
| 			q.lock.Unlock() | 			q.lock.Unlock() | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 	if _, ok := q.table[string(bs)]; ok { | 	if !q.table.Add(string(bs)) { | ||||||
| 		return ErrAlreadyInQueue | 		return ErrAlreadyInQueue | ||||||
| 	} | 	} | ||||||
| 	// FIXME: We probably need to implement some sort of limit here | 	// FIXME: We probably need to implement some sort of limit here | ||||||
| 	// If the downstream queue blocks this table will grow without limit | 	// If the downstream queue blocks this table will grow without limit | ||||||
| 	q.table[string(bs)] = true |  | ||||||
| 	if fn != nil { | 	if fn != nil { | ||||||
| 		err := fn() | 		err := fn() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			delete(q.table, string(bs)) | 			q.table.Remove(string(bs)) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -155,8 +155,7 @@ func (q *ChannelUniqueQueue) Has(data Data) (bool, error) { | |||||||
|  |  | ||||||
| 	q.lock.Lock() | 	q.lock.Lock() | ||||||
| 	defer q.lock.Unlock() | 	defer q.lock.Unlock() | ||||||
| 	_, has := q.table[string(bs)] | 	return q.table.Contains(string(bs)), nil | ||||||
| 	return has, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager | // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/organization" | 	"code.gitea.io/gitea/models/organization" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/git" | ||||||
| 	"code.gitea.io/gitea/modules/lfs" | 	"code.gitea.io/gitea/modules/lfs" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| @@ -275,7 +276,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) | |||||||
| 		return pullMirrorReleaseSync(repo, gitRepo) | 		return pullMirrorReleaseSync(repo, gitRepo) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	existingRelTags := make(map[string]struct{}) | 	existingRelTags := make(container.Set[string]) | ||||||
| 	opts := repo_model.FindReleasesOptions{ | 	opts := repo_model.FindReleasesOptions{ | ||||||
| 		IncludeDrafts: true, | 		IncludeDrafts: true, | ||||||
| 		IncludeTags:   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) | 					return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} 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 { | 	_, err := gitRepo.WalkReferences(git.ObjectTag, 0, 0, func(sha1, refname string) error { | ||||||
| 		tagName := strings.TrimPrefix(refname, git.TagPrefix) | 		tagName := strings.TrimPrefix(refname, git.TagPrefix) | ||||||
| 		if _, ok := existingRelTags[strings.ToLower(tagName)]; ok { | 		if existingRelTags.Contains(strings.ToLower(tagName)) { | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  |  | ||||||
| 	ini "gopkg.in/ini.v1" | 	ini "gopkg.in/ini.v1" | ||||||
| @@ -109,8 +110,8 @@ func NewQueueService() { | |||||||
| 	// Now handle the old issue_indexer configuration | 	// Now handle the old issue_indexer configuration | ||||||
| 	// FIXME: DEPRECATED to be removed in v1.18.0 | 	// FIXME: DEPRECATED to be removed in v1.18.0 | ||||||
| 	section := Cfg.Section("queue.issue_indexer") | 	section := Cfg.Section("queue.issue_indexer") | ||||||
| 	directlySet := toDirectlySetKeysMap(section) | 	directlySet := toDirectlySetKeysSet(section) | ||||||
| 	if !directlySet["TYPE"] && defaultType == "" { | 	if !directlySet.Contains("TYPE") && defaultType == "" { | ||||||
| 		switch typ := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_TYPE").MustString(""); typ { | 		switch typ := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_TYPE").MustString(""); typ { | ||||||
| 		case "levelqueue": | 		case "levelqueue": | ||||||
| 			_, _ = section.NewKey("TYPE", "level") | 			_, _ = section.NewKey("TYPE", "level") | ||||||
| @@ -124,25 +125,25 @@ func NewQueueService() { | |||||||
| 			log.Fatal("Unsupported indexer queue type: %v", typ) | 			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) | 		length := Cfg.Section("indexer").Key("UPDATE_BUFFER_LEN").MustInt(0) | ||||||
| 		if length != 0 { | 		if length != 0 { | ||||||
| 			_, _ = section.NewKey("LENGTH", strconv.Itoa(length)) | 			_, _ = 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) | 		fallback := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_BATCH_NUMBER").MustInt(0) | ||||||
| 		if fallback != 0 { | 		if fallback != 0 { | ||||||
| 			_, _ = section.NewKey("BATCH_LENGTH", strconv.Itoa(fallback)) | 			_, _ = 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("")) | 		queueDir := filepath.ToSlash(Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_DIR").MustString("")) | ||||||
| 		if queueDir != "" { | 		if queueDir != "" { | ||||||
| 			_, _ = section.NewKey("DATADIR", 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("") | 		connStr := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString("") | ||||||
| 		if connStr != "" { | 		if connStr != "" { | ||||||
| 			_, _ = section.NewKey("CONN_STR", connStr) | 			_, _ = section.NewKey("CONN_STR", connStr) | ||||||
| @@ -178,19 +179,19 @@ func handleOldLengthConfiguration(queueName, oldSection, oldKey string, defaultV | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	section := Cfg.Section("queue." + queueName) | 	section := Cfg.Section("queue." + queueName) | ||||||
| 	directlySet := toDirectlySetKeysMap(section) | 	directlySet := toDirectlySetKeysSet(section) | ||||||
| 	if !directlySet["LENGTH"] { | 	if !directlySet.Contains("LENGTH") { | ||||||
| 		_, _ = section.NewKey("LENGTH", strconv.Itoa(value)) | 		_, _ = 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 | // 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. | // but this section does not. | ||||||
| func toDirectlySetKeysMap(section *ini.Section) map[string]bool { | func toDirectlySetKeysSet(section *ini.Section) container.Set[string] { | ||||||
| 	sectionMap := map[string]bool{} | 	sections := make(container.Set[string]) | ||||||
| 	for _, key := range section.Keys() { | 	for _, key := range section.Keys() { | ||||||
| 		sectionMap[key.Name()] = true | 		sections.Add(key.Name()) | ||||||
| 	} | 	} | ||||||
| 	return sectionMap | 	return sections | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| 	"text/template" | 	"text/template" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/user" | 	"code.gitea.io/gitea/modules/user" | ||||||
| @@ -234,7 +235,7 @@ var ( | |||||||
| 		DefaultTheme          string | 		DefaultTheme          string | ||||||
| 		Themes                []string | 		Themes                []string | ||||||
| 		Reactions             []string | 		Reactions             []string | ||||||
| 		ReactionsMap          map[string]bool `ini:"-"` | 		ReactionsLookup       container.Set[string] `ini:"-"` | ||||||
| 		CustomEmojis          []string | 		CustomEmojis          []string | ||||||
| 		CustomEmojisMap       map[string]string `ini:"-"` | 		CustomEmojisMap       map[string]string `ini:"-"` | ||||||
| 		SearchRepoDescription bool | 		SearchRepoDescription bool | ||||||
| @@ -1100,9 +1101,9 @@ func loadFromConf(allowEmpty bool, extraConfig string) { | |||||||
|  |  | ||||||
| 	newMarkup() | 	newMarkup() | ||||||
|  |  | ||||||
| 	UI.ReactionsMap = make(map[string]bool) | 	UI.ReactionsLookup = make(container.Set[string]) | ||||||
| 	for _, reaction := range UI.Reactions { | 	for _, reaction := range UI.Reactions { | ||||||
| 		UI.ReactionsMap[reaction] = true | 		UI.ReactionsLookup.Add(reaction) | ||||||
| 	} | 	} | ||||||
| 	UI.CustomEmojisMap = make(map[string]string) | 	UI.CustomEmojisMap = make(map[string]string) | ||||||
| 	for _, emoji := range UI.CustomEmojis { | 	for _, emoji := range UI.CustomEmojis { | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ package sync | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"sync" | 	"sync" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // StatusTable is a table maintains true/false values. | // StatusTable is a table maintains true/false values. | ||||||
| @@ -14,13 +16,13 @@ import ( | |||||||
| // in different goroutines. | // in different goroutines. | ||||||
| type StatusTable struct { | type StatusTable struct { | ||||||
| 	lock sync.RWMutex | 	lock sync.RWMutex | ||||||
| 	pool map[string]struct{} | 	pool container.Set[string] | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewStatusTable initializes and returns a new StatusTable object. | // NewStatusTable initializes and returns a new StatusTable object. | ||||||
| func NewStatusTable() *StatusTable { | func NewStatusTable() *StatusTable { | ||||||
| 	return &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 | // Returns whether set value was set to true | ||||||
| func (p *StatusTable) StartIfNotRunning(name string) bool { | func (p *StatusTable) StartIfNotRunning(name string) bool { | ||||||
| 	p.lock.Lock() | 	p.lock.Lock() | ||||||
| 	_, ok := p.pool[name] | 	added := p.pool.Add(name) | ||||||
| 	if !ok { |  | ||||||
| 		p.pool[name] = struct{}{} |  | ||||||
| 	} |  | ||||||
| 	p.lock.Unlock() | 	p.lock.Unlock() | ||||||
| 	return !ok | 	return added | ||||||
| } | } | ||||||
|  |  | ||||||
| // Start sets value of given name to true in the pool. | // Start sets value of given name to true in the pool. | ||||||
| func (p *StatusTable) Start(name string) { | func (p *StatusTable) Start(name string) { | ||||||
| 	p.lock.Lock() | 	p.lock.Lock() | ||||||
| 	p.pool[name] = struct{}{} | 	p.pool.Add(name) | ||||||
| 	p.lock.Unlock() | 	p.lock.Unlock() | ||||||
| } | } | ||||||
|  |  | ||||||
| // Stop sets value of given name to false in the pool. | // Stop sets value of given name to false in the pool. | ||||||
| func (p *StatusTable) Stop(name string) { | func (p *StatusTable) Stop(name string) { | ||||||
| 	p.lock.Lock() | 	p.lock.Lock() | ||||||
| 	delete(p.pool, name) | 	p.pool.Remove(name) | ||||||
| 	p.lock.Unlock() | 	p.lock.Unlock() | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsRunning checks if value of given name is set to true in the pool. | // IsRunning checks if value of given name is set to true in the pool. | ||||||
| func (p *StatusTable) IsRunning(name string) bool { | func (p *StatusTable) IsRunning(name string) bool { | ||||||
| 	p.lock.RLock() | 	p.lock.RLock() | ||||||
| 	_, ok := p.pool[name] | 	exists := p.pool.Contains(name) | ||||||
| 	p.lock.RUnlock() | 	p.lock.RUnlock() | ||||||
| 	return ok | 	return exists | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	packages_model "code.gitea.io/gitea/models/packages" | 	packages_model "code.gitea.io/gitea/models/packages" | ||||||
| 	conan_model "code.gitea.io/gitea/models/packages/conan" | 	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/context" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| @@ -33,20 +34,18 @@ const ( | |||||||
| 	packageReferenceKey = "PackageReference" | 	packageReferenceKey = "PackageReference" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type stringSet map[string]struct{} |  | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	recipeFileList = stringSet{ | 	recipeFileList = container.SetOf( | ||||||
| 		conanfileFile:       struct{}{}, | 		conanfileFile, | ||||||
| 		"conanmanifest.txt": struct{}{}, | 		"conanmanifest.txt", | ||||||
| 		"conan_sources.tgz": struct{}{}, | 		"conan_sources.tgz", | ||||||
| 		"conan_export.tgz":  struct{}{}, | 		"conan_export.tgz", | ||||||
| 	} | 	) | ||||||
| 	packageFileList = stringSet{ | 	packageFileList = container.SetOf( | ||||||
| 		conaninfoFile:       struct{}{}, | 		conaninfoFile, | ||||||
| 		"conanmanifest.txt": struct{}{}, | 		"conanmanifest.txt", | ||||||
| 		"conan_package.tgz": struct{}{}, | 		"conan_package.tgz", | ||||||
| 	} | 	) | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func jsonResponse(ctx *context.Context, status int, obj interface{}) { | 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() | 	defer ctx.Req.Body.Close() | ||||||
|  |  | ||||||
| 	var files map[string]int64 | 	var files map[string]int64 | ||||||
| @@ -279,7 +278,7 @@ func serveUploadURLs(ctx *context.Context, fileFilter stringSet, uploadURL strin | |||||||
|  |  | ||||||
| 	urls := make(map[string]string) | 	urls := make(map[string]string) | ||||||
| 	for file := range files { | 	for file := range files { | ||||||
| 		if _, ok := fileFilter[file]; ok { | 		if fileFilter.Contains(file) { | ||||||
| 			urls[file] = fmt.Sprintf("%s/%s", uploadURL, file) | 			urls[file] = fmt.Sprintf("%s/%s", uploadURL, file) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -301,12 +300,12 @@ func UploadPackageFile(ctx *context.Context) { | |||||||
| 	uploadFile(ctx, packageFileList, pref.AsKey()) | 	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) | 	rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference) | ||||||
| 	pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference) | 	pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference) | ||||||
|  |  | ||||||
| 	filename := ctx.Params("filename") | 	filename := ctx.Params("filename") | ||||||
| 	if _, ok := fileFilter[filename]; !ok { | 	if !fileFilter.Contains(filename) { | ||||||
| 		apiError(ctx, http.StatusBadRequest, nil) | 		apiError(ctx, http.StatusBadRequest, nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -442,11 +441,11 @@ func DownloadPackageFile(ctx *context.Context) { | |||||||
| 	downloadFile(ctx, packageFileList, pref.AsKey()) | 	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) | 	rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference) | ||||||
|  |  | ||||||
| 	filename := ctx.Params("filename") | 	filename := ctx.Params("filename") | ||||||
| 	if _, ok := fileFilter[filename]; !ok { | 	if !fileFilter.Contains(filename) { | ||||||
| 		apiError(ctx, http.StatusBadRequest, nil) | 		apiError(ctx, http.StatusBadRequest, nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/convert" | 	"code.gitea.io/gitea/modules/convert" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| @@ -947,10 +948,11 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, nil, 0, 0 | 			return nil, nil, 0, 0 | ||||||
| 		} | 		} | ||||||
| 		labelIDMark := base.Int64sToMap(labelIDs) | 		labelIDMark := make(container.Set[int64]) | ||||||
|  | 		labelIDMark.AddMultiple(labelIDs...) | ||||||
|  |  | ||||||
| 		for i := range labels { | 		for i := range labels { | ||||||
| 			if labelIDMark[labels[i].ID] { | 			if labelIDMark.Contains(labels[i].ID) { | ||||||
| 				labels[i].IsChecked = true | 				labels[i].IsChecked = true | ||||||
| 				hasSelected = true | 				hasSelected = true | ||||||
| 			} | 			} | ||||||
| @@ -1293,9 +1295,9 @@ func ViewIssue(ctx *context.Context) { | |||||||
|  |  | ||||||
| 	// Metas. | 	// Metas. | ||||||
| 	// Check labels. | 	// Check labels. | ||||||
| 	labelIDMark := make(map[int64]bool) | 	labelIDMark := make(container.Set[int64]) | ||||||
| 	for i := range issue.Labels { | 	for _, label := range issue.Labels { | ||||||
| 		labelIDMark[issue.Labels[i].ID] = true | 		labelIDMark.Add(label.ID) | ||||||
| 	} | 	} | ||||||
| 	labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{}) | 	labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -1317,7 +1319,7 @@ func ViewIssue(ctx *context.Context) { | |||||||
|  |  | ||||||
| 	hasSelected := false | 	hasSelected := false | ||||||
| 	for i := range labels { | 	for i := range labels { | ||||||
| 		if labelIDMark[labels[i].ID] { | 		if labelIDMark.Contains(labels[i].ID) { | ||||||
| 			labels[i].IsChecked = true | 			labels[i].IsChecked = true | ||||||
| 			hasSelected = true | 			hasSelected = true | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import ( | |||||||
| 	git_model "code.gitea.io/gitea/models/git" | 	git_model "code.gitea.io/gitea/models/git" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/charset" | 	"code.gitea.io/gitea/modules/charset" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/git/pipeline" | 	"code.gitea.io/gitea/modules/git/pipeline" | ||||||
| @@ -176,14 +177,12 @@ func LFSLocks(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	filemap := make(map[string]bool, len(filelist)) | 	fileset := make(container.Set[string], len(filelist)) | ||||||
| 	for _, name := range filelist { | 	fileset.AddMultiple(filelist...) | ||||||
| 		filemap[name] = true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	linkable := make([]bool, len(lfsLocks)) | 	linkable := make([]bool, len(lfsLocks)) | ||||||
| 	for i, lock := range lfsLocks { | 	for i, lock := range lfsLocks { | ||||||
| 		linkable[i] = filemap[lock.Path] | 		linkable[i] = fileset.Contains(lock.Path) | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["Linkable"] = linkable | 	ctx.Data["Linkable"] = linkable | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ import ( | |||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/charset" | 	"code.gitea.io/gitea/modules/charset" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/highlight" | 	"code.gitea.io/gitea/modules/highlight" | ||||||
| @@ -811,16 +812,14 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri | |||||||
| 		defer cancel() | 		defer cancel() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	selected := map[string]bool{} | 	selected := make(container.Set[string]) | ||||||
| 	for _, pth := range ctx.FormStrings("f[]") { | 	selected.AddMultiple(ctx.FormStrings("f[]")...) | ||||||
| 		selected[pth] = true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	entries := allEntries | 	entries := allEntries | ||||||
| 	if len(selected) > 0 { | 	if len(selected) > 0 { | ||||||
| 		entries = make(git.Entries, 0, len(selected)) | 		entries = make(git.Entries, 0, len(selected)) | ||||||
| 		for _, entry := range allEntries { | 		for _, entry := range allEntries { | ||||||
| 			if selected[entry.Name()] { | 			if selected.Contains(entry.Name()) { | ||||||
| 				entries = append(entries, entry) | 				entries = append(entries, entry) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import ( | |||||||
| 	access_model "code.gitea.io/gitea/models/perm/access" | 	access_model "code.gitea.io/gitea/models/perm/access" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/log" | ||||||
| 	"code.gitea.io/gitea/modules/references" | 	"code.gitea.io/gitea/modules/references" | ||||||
| 	"code.gitea.io/gitea/modules/repository" | 	"code.gitea.io/gitea/modules/repository" | ||||||
| @@ -111,7 +112,7 @@ func UpdateIssuesCommit(doer *user_model.User, repo *repo_model.Repository, comm | |||||||
| 			Action references.XRefAction | 			Action references.XRefAction | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		refMarked := make(map[markKey]bool) | 		refMarked := make(container.Set[markKey]) | ||||||
| 		var refRepo *repo_model.Repository | 		var refRepo *repo_model.Repository | ||||||
| 		var refIssue *issues_model.Issue | 		var refIssue *issues_model.Issue | ||||||
| 		var err error | 		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} | 			key := markKey{ID: refIssue.ID, Action: ref.Action} | ||||||
| 			if refMarked[key] { | 			if !refMarked.Add(key) { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			refMarked[key] = true |  | ||||||
|  |  | ||||||
| 			// FIXME: this kind of condition is all over the code, it should be consolidated in a single place | 			// 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 | 			canclose := perm.IsAdmin() || perm.IsOwner() || perm.CanWriteIssuesOrPulls(refIssue.IsPull) || refIssue.PosterID == doer.ID | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	activities_model "code.gitea.io/gitea/models/activities" | 	activities_model "code.gitea.io/gitea/models/activities" | ||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
| @@ -46,8 +47,8 @@ func MailMentionsComment(ctx context.Context, pr *issues_model.PullRequest, c *i | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	visited := make(map[int64]bool, len(mentions)+1) | 	visited := make(container.Set[int64], len(mentions)+1) | ||||||
| 	visited[c.Poster.ID] = true | 	visited.Add(c.Poster.ID) | ||||||
| 	if err = mailIssueCommentBatch( | 	if err = mailIssueCommentBatch( | ||||||
| 		&mailCommentContext{ | 		&mailCommentContext{ | ||||||
| 			Context:    ctx, | 			Context:    ctx, | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
| @@ -89,11 +90,11 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo | |||||||
| 		unfiltered = append(ids, unfiltered...) | 		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 | 	// Avoid mailing the doer | ||||||
| 	if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn { | 	if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn { | ||||||
| 		visited[ctx.Doer.ID] = true | 		visited.Add(ctx.Doer.ID) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// =========== Mentions =========== | 	// =========== Mentions =========== | ||||||
| @@ -106,9 +107,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("GetIssueWatchersIDs(%d): %v", ctx.Issue.ID, err) | 		return fmt.Errorf("GetIssueWatchersIDs(%d): %v", ctx.Issue.ID, err) | ||||||
| 	} | 	} | ||||||
| 	for _, i := range ids { | 	visited.AddMultiple(ids...) | ||||||
| 		visited[i] = true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	unfilteredUsers, err := user_model.GetMaileableUsersByIDs(unfiltered, false) | 	unfilteredUsers, err := user_model.GetMaileableUsersByIDs(unfiltered, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -121,7 +120,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo | |||||||
| 	return nil | 	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 | 	checkUnit := unit.TypeIssues | ||||||
| 	if ctx.Issue.IsPull { | 	if ctx.Issue.IsPull { | ||||||
| 		checkUnit = unit.TypePullRequests | 		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 we have already visited this user we exclude them | ||||||
| 		if _, ok := visited[user.ID]; ok { | 		if !visited.Add(user.ID) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// now mark them as visited |  | ||||||
| 		visited[user.ID] = true |  | ||||||
|  |  | ||||||
| 		// test if this user is allowed to see the issue/pull | 		// test if this user is allowed to see the issue/pull | ||||||
| 		if !access_model.CheckRepoUnitUser(ctx, ctx.Issue.Repo, user, checkUnit) { | 		if !access_model.CheckRepoUnitUser(ctx, ctx.Issue.Repo, user, checkUnit) { | ||||||
| 			continue | 			continue | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
|  | 	"code.gitea.io/gitea/modules/container" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/graceful" | 	"code.gitea.io/gitea/modules/graceful" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"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 appliedPatchPrefix = "Applied patch to '" | ||||||
| 				const withConflicts = "' with conflicts." | 				const withConflicts = "' with conflicts." | ||||||
|  |  | ||||||
| 				conflictMap := map[string]bool{} | 				conflicts := make(container.Set[string]) | ||||||
|  |  | ||||||
| 				// Now scan the output from the command | 				// Now scan the output from the command | ||||||
| 				scanner := bufio.NewScanner(stderrReader) | 				scanner := bufio.NewScanner(stderrReader) | ||||||
| @@ -418,7 +419,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * | |||||||
| 					if strings.HasPrefix(line, prefix) { | 					if strings.HasPrefix(line, prefix) { | ||||||
| 						conflict = true | 						conflict = true | ||||||
| 						filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) | 						filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) | ||||||
| 						conflictMap[filepath] = true | 						conflicts.Add(filepath) | ||||||
| 					} else if is3way && line == threewayFailed { | 					} else if is3way && line == threewayFailed { | ||||||
| 						conflict = true | 						conflict = true | ||||||
| 					} else if strings.HasPrefix(line, errorPrefix) { | 					} 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) { | 							if strings.HasSuffix(line, suffix) { | ||||||
| 								filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix)) | 								filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix)) | ||||||
| 								if filepath != "" { | 								if filepath != "" { | ||||||
| 									conflictMap[filepath] = true | 									conflicts.Add(filepath) | ||||||
| 								} | 								} | ||||||
| 								break | 								break | ||||||
| 							} | 							} | ||||||
| @@ -436,18 +437,18 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * | |||||||
| 						conflict = true | 						conflict = true | ||||||
| 						filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix) | 						filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix) | ||||||
| 						if filepath != "" { | 						if filepath != "" { | ||||||
| 							conflictMap[filepath] = true | 							conflicts.Add(filepath) | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 					// only list 10 conflicted files | 					// only list 10 conflicted files | ||||||
| 					if len(conflictMap) >= 10 { | 					if len(conflicts) >= 10 { | ||||||
| 						break | 						break | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if len(conflictMap) > 0 { | 				if len(conflicts) > 0 { | ||||||
| 					pr.ConflictedFiles = make([]string, 0, len(conflictMap)) | 					pr.ConflictedFiles = make([]string, 0, len(conflicts)) | ||||||
| 					for key := range conflictMap { | 					for key := range conflicts { | ||||||
| 						pr.ConflictedFiles = append(pr.ConflictedFiles, key) | 						pr.ConflictedFiles = append(pr.ConflictedFiles, key) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/git" | ||||||
| 	"code.gitea.io/gitea/modules/graceful" | 	"code.gitea.io/gitea/modules/graceful" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"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() | 	posterSig := pr.Issue.Poster.NewGitSig().String() | ||||||
|  |  | ||||||
| 	authorsMap := map[string]bool{} | 	uniqueAuthors := make(container.Set[string]) | ||||||
| 	authors := make([]string, 0, len(commits)) | 	authors := make([]string, 0, len(commits)) | ||||||
| 	stringBuilder := strings.Builder{} | 	stringBuilder := strings.Builder{} | ||||||
|  |  | ||||||
| @@ -687,9 +688,8 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		authorString := commit.Author.String() | 		authorString := commit.Author.String() | ||||||
| 		if !authorsMap[authorString] && authorString != posterSig { | 		if uniqueAuthors.Add(authorString) && authorString != posterSig { | ||||||
| 			authors = append(authors, authorString) | 			authors = append(authors, authorString) | ||||||
| 			authorsMap[authorString] = true |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -709,9 +709,8 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ | |||||||
| 			} | 			} | ||||||
| 			for _, commit := range commits { | 			for _, commit := range commits { | ||||||
| 				authorString := commit.Author.String() | 				authorString := commit.Author.String() | ||||||
| 				if !authorsMap[authorString] && authorString != posterSig { | 				if uniqueAuthors.Add(authorString) && authorString != posterSig { | ||||||
| 					authors = append(authors, authorString) | 					authors = append(authors, authorString) | ||||||
| 					authorsMap[authorString] = true |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			skip += limit | 			skip += limit | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import ( | |||||||
| 	git_model "code.gitea.io/gitea/models/git" | 	git_model "code.gitea.io/gitea/models/git" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/git" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/notification" | 	"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) | 		return fmt.Errorf("AddReleaseAttachments: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	deletedUUIDsMap := make(map[string]bool) | 	deletedUUIDs := make(container.Set[string]) | ||||||
| 	if len(delAttachmentUUIDs) > 0 { | 	if len(delAttachmentUUIDs) > 0 { | ||||||
| 		// Check attachments | 		// Check attachments | ||||||
| 		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, delAttachmentUUIDs) | 		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 { | 			if attach.ReleaseID != rel.ID { | ||||||
| 				return errors.New("delete attachement of release permission denied") | 				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 { | 		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 { | 		for uuid, newName := range editAttachments { | ||||||
| 			if !deletedUUIDsMap[uuid] { | 			if !deletedUUIDs.Contains(uuid) { | ||||||
| 				if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{ | 				if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{ | ||||||
| 					UUID: uuid, | 					UUID: uuid, | ||||||
| 					Name: newName, | 					Name: newName, | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	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/git" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/notification" | 	"code.gitea.io/gitea/modules/notification" | ||||||
| @@ -257,12 +258,12 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad | |||||||
| 	if len(repos) == len(repoNamesToCheck) { | 	if len(repos) == len(repoNamesToCheck) { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	repoNames := make(map[string]bool, len(repos)) | 	repoNames := make(container.Set[string], len(repos)) | ||||||
| 	for _, repo := range repos { | 	for _, repo := range repos { | ||||||
| 		repoNames[repo.LowerName] = true | 		repoNames.Add(repo.LowerName) | ||||||
| 	} | 	} | ||||||
| 	for _, repoName := range repoNamesToCheck { | 	for _, repoName := range repoNamesToCheck { | ||||||
| 		if _, ok := repoNames[repoName]; !ok { | 		if !repoNames.Contains(repoName) { | ||||||
| 			unadopted.add(filepath.Join(userName, repoName)) | 			unadopted.add(filepath.Join(userName, repoName)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user